[Allwinner] Add analogue audio to H6, enable Cedrus (#1750)

* [Allwinner] Add analogue audio to H6, enable Cedrus, remove deprecated patches, adjust config
* Cleanup
This commit is contained in:
Igor Pečovnik 2020-01-19 00:03:54 +01:00 committed by GitHub
parent cc7ab6a6b1
commit fc23107e1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 7927 additions and 49 deletions

View file

@ -1,6 +1,6 @@
#
# Automatically generated file; DO NOT EDIT.
# Linux/arm 5.4.10 Kernel Configuration
# Linux/arm 5.4.12 Kernel Configuration
#
#
@ -656,6 +656,7 @@ CONFIG_HAVE_ARCH_MMAP_RND_BITS=y
CONFIG_HAVE_EXIT_THREAD=y
CONFIG_ARCH_MMAP_RND_BITS=8
CONFIG_ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT=y
CONFIG_HAVE_COPY_THREAD_TLS=y
CONFIG_CLONE_BACKWARDS=y
CONFIG_OLD_SIGSUSPEND3=y
CONFIG_OLD_SIGACTION=y
@ -2338,6 +2339,7 @@ CONFIG_LED_TRIGGER_PHY=y
#
# CONFIG_SFP is not set
CONFIG_ADIN_PHY=m
CONFIG_AC200_PHY=m
CONFIG_AMD_PHY=m
CONFIG_AQUANTIA_PHY=m
CONFIG_AX88796B_PHY=m
@ -3435,6 +3437,7 @@ CONFIG_MFD_SUN4I_GPADC=m
# CONFIG_MFD_BCM590XX is not set
# CONFIG_MFD_BD9571MWV is not set
# CONFIG_MFD_AC100 is not set
CONFIG_MFD_AC200=m
CONFIG_MFD_AXP20X=y
CONFIG_MFD_AXP20X_I2C=y
CONFIG_MFD_AXP20X_RSB=y
@ -3639,12 +3642,12 @@ CONFIG_MEDIA_ANALOG_TV_SUPPORT=y
CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y
CONFIG_MEDIA_RADIO_SUPPORT=y
CONFIG_MEDIA_SDR_SUPPORT=y
# CONFIG_MEDIA_CEC_SUPPORT is not set
# CONFIG_MEDIA_CEC_RC is not set
CONFIG_MEDIA_CEC_SUPPORT=y
CONFIG_MEDIA_CEC_RC=y
# CONFIG_CEC_PIN_ERROR_INJ is not set
CONFIG_MEDIA_CONTROLLER=y
CONFIG_MEDIA_CONTROLLER_DVB=y
# CONFIG_MEDIA_CONTROLLER_REQUEST_API is not set
CONFIG_MEDIA_CONTROLLER_REQUEST_API=y
CONFIG_VIDEO_DEV=y
CONFIG_VIDEO_V4L2_SUBDEV_API=y
CONFIG_VIDEO_V4L2=y
@ -3827,6 +3830,12 @@ CONFIG_VIDEO_EM28XX_RC=m
CONFIG_USB_AIRSPY=m
CONFIG_USB_HACKRF=m
CONFIG_USB_MSI2500=m
#
# USB HDMI CEC adapters
#
CONFIG_USB_PULSE8_CEC=m
CONFIG_USB_RAINSHADOW_CEC=m
CONFIG_V4L_PLATFORM_DRIVERS=y
# CONFIG_VIDEO_CADENCE is not set
CONFIG_VIDEO_ASPEED=m
@ -3846,6 +3855,7 @@ CONFIG_VIDEO_VIM2M=m
CONFIG_VIDEO_VICODEC=m
CONFIG_DVB_PLATFORM_DRIVERS=y
CONFIG_DVB_C8SECTPFE=m
CONFIG_CEC_PLATFORM_DRIVERS=y
# CONFIG_SDR_PLATFORM_DRIVERS is not set
#
@ -5586,6 +5596,7 @@ CONFIG_AD9834=m
CONFIG_STAGING_MEDIA=y
CONFIG_VIDEO_SUNXI=y
CONFIG_VIDEO_SUNXI_CEDRUS=m
#
# soc_camera sensor drivers

View file

@ -1,6 +1,6 @@
#
# Automatically generated file; DO NOT EDIT.
# Linux/arm 5.5.0-rc5 Kernel Configuration
# Linux/arm 5.5.0-rc6 Kernel Configuration
#
#
@ -655,6 +655,7 @@ CONFIG_HAVE_ARCH_MMAP_RND_BITS=y
CONFIG_HAVE_EXIT_THREAD=y
CONFIG_ARCH_MMAP_RND_BITS=8
CONFIG_ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT=y
CONFIG_HAVE_COPY_THREAD_TLS=y
CONFIG_CLONE_BACKWARDS=y
CONFIG_OLD_SIGSUSPEND3=y
CONFIG_OLD_SIGACTION=y
@ -2305,6 +2306,7 @@ CONFIG_LED_TRIGGER_PHY=y
#
# CONFIG_SFP is not set
CONFIG_ADIN_PHY=m
CONFIG_AC200_PHY=m
CONFIG_AMD_PHY=m
CONFIG_AQUANTIA_PHY=m
CONFIG_AX88796B_PHY=m
@ -3410,6 +3412,7 @@ CONFIG_MFD_SUN4I_GPADC=m
# CONFIG_MFD_BCM590XX is not set
# CONFIG_MFD_BD9571MWV is not set
# CONFIG_MFD_AC100 is not set
CONFIG_MFD_AC200=m
CONFIG_MFD_AXP20X=y
CONFIG_MFD_AXP20X_I2C=y
CONFIG_MFD_AXP20X_RSB=y
@ -3619,7 +3622,7 @@ CONFIG_MEDIA_SDR_SUPPORT=y
# CONFIG_CEC_PIN_ERROR_INJ is not set
CONFIG_MEDIA_CONTROLLER=y
CONFIG_MEDIA_CONTROLLER_DVB=y
# CONFIG_MEDIA_CONTROLLER_REQUEST_API is not set
CONFIG_MEDIA_CONTROLLER_REQUEST_API=y
CONFIG_VIDEO_DEV=y
CONFIG_VIDEO_V4L2_SUBDEV_API=y
CONFIG_VIDEO_V4L2=y
@ -4689,6 +4692,7 @@ CONFIG_SND_SOC_ES8328=m
CONFIG_SND_SOC_ES8328_I2C=m
CONFIG_SND_SOC_ES8328_SPI=m
# CONFIG_SND_SOC_GTM601 is not set
CONFIG_SND_SOC_EC25=m
# CONFIG_SND_SOC_INNO_RK3036 is not set
CONFIG_SND_SOC_MAX98088=m
# CONFIG_SND_SOC_MAX98357A is not set
@ -5574,6 +5578,7 @@ CONFIG_AD9834=m
CONFIG_STAGING_MEDIA=y
CONFIG_VIDEO_SUNXI=y
CONFIG_VIDEO_SUNXI_CEDRUS=m
#
# soc_camera sensor drivers

View file

@ -1,6 +1,6 @@
#
# Automatically generated file; DO NOT EDIT.
# Linux/arm64 5.4.10 Kernel Configuration
# Linux/arm64 5.4.12 Kernel Configuration
#
#
@ -600,6 +600,7 @@ CONFIG_ARCH_MMAP_RND_BITS=18
CONFIG_HAVE_ARCH_MMAP_RND_COMPAT_BITS=y
CONFIG_ARCH_MMAP_RND_COMPAT_BITS=11
CONFIG_ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT=y
CONFIG_HAVE_COPY_THREAD_TLS=y
CONFIG_CLONE_BACKWARDS=y
CONFIG_OLD_SIGSUSPEND3=y
CONFIG_COMPAT_OLD_SIGACTION=y
@ -2235,6 +2236,7 @@ CONFIG_LED_TRIGGER_PHY=y
#
# CONFIG_SFP is not set
CONFIG_ADIN_PHY=m
CONFIG_AC200_PHY=m
CONFIG_AMD_PHY=m
CONFIG_AQUANTIA_PHY=m
CONFIG_AX88796B_PHY=m
@ -3356,6 +3358,7 @@ CONFIG_MFD_SUN4I_GPADC=y
# CONFIG_MFD_BCM590XX is not set
CONFIG_MFD_BD9571MWV=m
# CONFIG_MFD_AC100 is not set
CONFIG_MFD_AC200=m
CONFIG_MFD_AXP20X=y
CONFIG_MFD_AXP20X_I2C=y
CONFIG_MFD_AXP20X_RSB=y
@ -3561,6 +3564,7 @@ CONFIG_MEDIA_SDR_SUPPORT=y
# CONFIG_CEC_PIN_ERROR_INJ is not set
CONFIG_MEDIA_CONTROLLER=y
CONFIG_MEDIA_CONTROLLER_DVB=y
CONFIG_MEDIA_CONTROLLER_REQUEST_API=y
CONFIG_VIDEO_DEV=m
CONFIG_VIDEO_V4L2_SUBDEV_API=y
CONFIG_VIDEO_V4L2=m
@ -3568,6 +3572,7 @@ CONFIG_VIDEO_V4L2_I2C=y
# CONFIG_VIDEO_ADV_DEBUG is not set
CONFIG_VIDEO_FIXED_MINOR_RANGES=y
CONFIG_VIDEO_TUNER=m
CONFIG_V4L2_MEM2MEM_DEV=m
CONFIG_V4L2_FWNODE=m
CONFIG_VIDEOBUF_GEN=m
CONFIG_VIDEOBUF_VMALLOC=m
@ -5377,7 +5382,13 @@ CONFIG_AD9834=m
# CONFIG_SPEAKUP is not set
# end of Speakup console speech
# CONFIG_STAGING_MEDIA is not set
CONFIG_STAGING_MEDIA=y
CONFIG_VIDEO_SUNXI=y
CONFIG_VIDEO_SUNXI_CEDRUS=m
#
# soc_camera sensor drivers
#
#
# Android

View file

@ -1,35 +0,0 @@
From 98800eeb2244387e821f4af8d21ccf2deaf18da0 Mon Sep 17 00:00:00 2001
From: Icenowy Zheng <icenowy@aosc.io>
Date: Mon, 21 Aug 2017 23:02:32 +0800
Subject: [PATCH 003/146] net: stmmac: dwmac-sun8i: support RGMII modes with
PHY internal delay
Some boards uses a PHY with internal delay with an Allwinner SoC.
Support these PHY modes in the driver.
As the driver has no configuration registers for these modes, just treat
them as ordinary RGMII.
Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
index f9a61f90cfbc..3c18f4a9dd6c 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
@@ -933,6 +933,9 @@ static int sun8i_dwmac_set_syscon(struct stmmac_priv *priv)
/* default */
break;
case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
reg |= SYSCON_EPIT | SYSCON_ETCS_INT_GMII;
break;
case PHY_INTERFACE_MODE_RMII:
--
2.17.1

View file

@ -0,0 +1,57 @@
From 61fd036d01111679b01e4b92e6bd0cdd33809aea Mon Sep 17 00:00:00 2001
From: Ricardo Ribalda Delgado <ribalda@kernel.org>
Date: Mon, 7 Oct 2019 12:06:33 -0300
Subject: [PATCH] media: add V4L2_CID_UNIT_CELL_SIZE control
This control returns the unit cell size in nanometres. The struct provides
the width and the height in separated fields to take into consideration
asymmetric pixels and/or hardware binning.
This control is required for automatic calibration of sensors/cameras.
Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de>
Signed-off-by: Ricardo Ribalda Delgado <ribalda@kernel.org>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
drivers/media/v4l2-core/v4l2-ctrls.c | 5 +++++
include/uapi/linux/v4l2-controls.h | 1 +
2 files changed, 6 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-ctrls.c b/drivers/media/v4l2-core/v4l2-ctrls.c
index 96cab2e173d3..bf50d37ef6c1 100644
--- a/drivers/media/v4l2-core/v4l2-ctrls.c
+++ b/drivers/media/v4l2-core/v4l2-ctrls.c
@@ -996,6 +996,7 @@ const char *v4l2_ctrl_get_name(u32 id)
case V4L2_CID_AUTO_FOCUS_RANGE: return "Auto Focus, Range";
case V4L2_CID_PAN_SPEED: return "Pan, Speed";
case V4L2_CID_TILT_SPEED: return "Tilt, Speed";
+ case V4L2_CID_UNIT_CELL_SIZE: return "Unit Cell Size";
/* FM Radio Modulator controls */
/* Keep the order of the 'case's the same as in v4l2-controls.h! */
@@ -1377,6 +1378,10 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
case V4L2_CID_MPEG_VIDEO_VP8_FRAME_HEADER:
*type = V4L2_CTRL_TYPE_VP8_FRAME_HEADER;
break;
+ case V4L2_CID_UNIT_CELL_SIZE:
+ *type = V4L2_CTRL_TYPE_AREA;
+ *flags |= V4L2_CTRL_FLAG_READ_ONLY;
+ break;
default:
*type = V4L2_CTRL_TYPE_INTEGER;
break;
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index a2669b79b294..5a7bedee2b0e 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -1034,6 +1034,7 @@ enum v4l2_jpeg_chroma_subsampling {
#define V4L2_CID_TEST_PATTERN_GREENR (V4L2_CID_IMAGE_SOURCE_CLASS_BASE + 5)
#define V4L2_CID_TEST_PATTERN_BLUE (V4L2_CID_IMAGE_SOURCE_CLASS_BASE + 6)
#define V4L2_CID_TEST_PATTERN_GREENB (V4L2_CID_IMAGE_SOURCE_CLASS_BASE + 7)
+#define V4L2_CID_UNIT_CELL_SIZE (V4L2_CID_IMAGE_SOURCE_CLASS_BASE + 8)
/* Image processing controls */
--
2.23.0

View file

@ -0,0 +1,165 @@
From d1dc49370f8371b00e682ac409aa1987ce641e93 Mon Sep 17 00:00:00 2001
From: Ricardo Ribalda Delgado <ribalda@kernel.org>
Date: Mon, 7 Oct 2019 12:06:31 -0300
Subject: [PATCH] media: add V4L2_CTRL_TYPE_AREA control type
This type contains the width and the height of a rectangular area.
Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
Signed-off-by: Ricardo Ribalda Delgado <ribalda@kernel.org>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
drivers/media/v4l2-core/v4l2-ctrls.c | 21 ++++++++++++++
include/media/v4l2-ctrls.h | 42 ++++++++++++++++++++++++++++
include/uapi/linux/videodev2.h | 6 ++++
3 files changed, 69 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-ctrls.c b/drivers/media/v4l2-core/v4l2-ctrls.c
index 219d8aeefa20..96cab2e173d3 100644
--- a/drivers/media/v4l2-core/v4l2-ctrls.c
+++ b/drivers/media/v4l2-core/v4l2-ctrls.c
@@ -1677,6 +1677,7 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx,
{
struct v4l2_ctrl_mpeg2_slice_params *p_mpeg2_slice_params;
struct v4l2_ctrl_vp8_frame_header *p_vp8_frame_header;
+ struct v4l2_area *area;
void *p = ptr.p + idx * ctrl->elem_size;
switch ((u32)ctrl->type) {
@@ -1753,6 +1754,11 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx,
zero_padding(p_vp8_frame_header->entropy_header);
zero_padding(p_vp8_frame_header->coder_state);
break;
+ case V4L2_CTRL_TYPE_AREA:
+ area = p;
+ if (!area->width || !area->height)
+ return -EINVAL;
+ break;
default:
return -EINVAL;
}
@@ -2427,6 +2433,9 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
case V4L2_CTRL_TYPE_VP8_FRAME_HEADER:
elem_size = sizeof(struct v4l2_ctrl_vp8_frame_header);
break;
+ case V4L2_CTRL_TYPE_AREA:
+ elem_size = sizeof(struct v4l2_area);
+ break;
default:
if (type < V4L2_CTRL_COMPOUND_TYPES)
elem_size = sizeof(s32);
@@ -4116,6 +4125,18 @@ int __v4l2_ctrl_s_ctrl_string(struct v4l2_ctrl *ctrl, const char *s)
}
EXPORT_SYMBOL(__v4l2_ctrl_s_ctrl_string);
+int __v4l2_ctrl_s_ctrl_area(struct v4l2_ctrl *ctrl,
+ const struct v4l2_area *area)
+{
+ lockdep_assert_held(ctrl->handler->lock);
+
+ /* It's a driver bug if this happens. */
+ WARN_ON(ctrl->type != V4L2_CTRL_TYPE_AREA);
+ *ctrl->p_new.p_area = *area;
+ return set_ctrl(NULL, ctrl, 0);
+}
+EXPORT_SYMBOL(__v4l2_ctrl_s_ctrl_area);
+
void v4l2_ctrl_request_complete(struct media_request *req,
struct v4l2_ctrl_handler *main_hdl)
{
diff --git a/include/media/v4l2-ctrls.h b/include/media/v4l2-ctrls.h
index fb0883836548..c9ca867ef32b 100644
--- a/include/media/v4l2-ctrls.h
+++ b/include/media/v4l2-ctrls.h
@@ -50,6 +50,7 @@ struct poll_table_struct;
* @p_h264_slice_params: Pointer to a struct v4l2_ctrl_h264_slice_params.
* @p_h264_decode_params: Pointer to a struct v4l2_ctrl_h264_decode_params.
* @p_vp8_frame_header: Pointer to a VP8 frame header structure.
+ * @p_area: Pointer to an area.
* @p: Pointer to a compound value.
*/
union v4l2_ctrl_ptr {
@@ -68,6 +69,7 @@ union v4l2_ctrl_ptr {
struct v4l2_ctrl_h264_slice_params *p_h264_slice_params;
struct v4l2_ctrl_h264_decode_params *p_h264_decode_params;
struct v4l2_ctrl_vp8_frame_header *p_vp8_frame_header;
+ struct v4l2_area *p_area;
void *p;
};
@@ -1087,6 +1089,46 @@ static inline int v4l2_ctrl_s_ctrl_string(struct v4l2_ctrl *ctrl, const char *s)
return rval;
}
+/**
+ * __v4l2_ctrl_s_ctrl_area() - Unlocked variant of v4l2_ctrl_s_ctrl_area().
+ *
+ * @ctrl: The control.
+ * @area: The new area.
+ *
+ * This sets the control's new area safely by going through the control
+ * framework. This function assumes the control's handler is already locked,
+ * allowing it to be used from within the &v4l2_ctrl_ops functions.
+ *
+ * This function is for area type controls only.
+ */
+int __v4l2_ctrl_s_ctrl_area(struct v4l2_ctrl *ctrl,
+ const struct v4l2_area *area);
+
+/**
+ * v4l2_ctrl_s_ctrl_area() - Helper function to set a control's area value
+ * from within a driver.
+ *
+ * @ctrl: The control.
+ * @area: The new area.
+ *
+ * This sets the control's new area safely by going through the control
+ * framework. This function will lock the control's handler, so it cannot be
+ * used from within the &v4l2_ctrl_ops functions.
+ *
+ * This function is for area type controls only.
+ */
+static inline int v4l2_ctrl_s_ctrl_area(struct v4l2_ctrl *ctrl,
+ const struct v4l2_area *area)
+{
+ int rval;
+
+ v4l2_ctrl_lock(ctrl);
+ rval = __v4l2_ctrl_s_ctrl_area(ctrl, area);
+ v4l2_ctrl_unlock(ctrl);
+
+ return rval;
+}
+
/* Internal helper functions that deal with control events. */
extern const struct v4l2_subscribed_event_ops v4l2_ctrl_sub_ev_ops;
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index 530638dffd93..b3c0961b62a0 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -422,6 +422,11 @@ struct v4l2_fract {
__u32 denominator;
};
+struct v4l2_area {
+ __u32 width;
+ __u32 height;
+};
+
/**
* struct v4l2_capability - Describes V4L2 device caps returned by VIDIOC_QUERYCAP
*
@@ -1720,6 +1725,7 @@ enum v4l2_ctrl_type {
V4L2_CTRL_TYPE_U8 = 0x0100,
V4L2_CTRL_TYPE_U16 = 0x0101,
V4L2_CTRL_TYPE_U32 = 0x0102,
+ V4L2_CTRL_TYPE_AREA = 0x0106,
};
/* Used in the VIDIOC_QUERYCTRL ioctl for querying controls */
--
2.23.0

View file

@ -0,0 +1,265 @@
From f8cca8c97a63d77f48334cde81d15014f43530ef Mon Sep 17 00:00:00 2001
From: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Date: Fri, 11 Oct 2019 06:32:41 -0300
Subject: [PATCH] media: v4l2-mem2mem: support held capture buffers
Check for held buffers that are ready to be returned to vb2 in
__v4l2_m2m_try_queue(). This avoids drivers having to handle this
case.
Add v4l2_m2m_buf_done_and_job_finish() to correctly return source
and destination buffers and mark the job as finished while taking
a held destination buffer into account (i.e. that buffer won't be
returned). This has to be done while job_spinlock is held to avoid
race conditions.
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
drivers/media/v4l2-core/v4l2-mem2mem.c | 130 ++++++++++++++++++-------
include/media/v4l2-mem2mem.h | 33 ++++++-
2 files changed, 128 insertions(+), 35 deletions(-)
diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c
index 19937dd3c6f6..79c3656f24f7 100644
--- a/drivers/media/v4l2-core/v4l2-mem2mem.c
+++ b/drivers/media/v4l2-core/v4l2-mem2mem.c
@@ -284,7 +284,8 @@ static void v4l2_m2m_try_run(struct v4l2_m2m_dev *m2m_dev)
static void __v4l2_m2m_try_queue(struct v4l2_m2m_dev *m2m_dev,
struct v4l2_m2m_ctx *m2m_ctx)
{
- unsigned long flags_job, flags_out, flags_cap;
+ unsigned long flags_job;
+ struct vb2_v4l2_buffer *dst, *src;
dprintk("Trying to schedule a job for m2m_ctx: %p\n", m2m_ctx);
@@ -307,20 +308,30 @@ static void __v4l2_m2m_try_queue(struct v4l2_m2m_dev *m2m_dev,
goto job_unlock;
}
- spin_lock_irqsave(&m2m_ctx->out_q_ctx.rdy_spinlock, flags_out);
- if (list_empty(&m2m_ctx->out_q_ctx.rdy_queue)
- && !m2m_ctx->out_q_ctx.buffered) {
+ src = v4l2_m2m_next_src_buf(m2m_ctx);
+ dst = v4l2_m2m_next_dst_buf(m2m_ctx);
+ if (!src && !m2m_ctx->out_q_ctx.buffered) {
dprintk("No input buffers available\n");
- goto out_unlock;
+ goto job_unlock;
}
- spin_lock_irqsave(&m2m_ctx->cap_q_ctx.rdy_spinlock, flags_cap);
- if (list_empty(&m2m_ctx->cap_q_ctx.rdy_queue)
- && !m2m_ctx->cap_q_ctx.buffered) {
+ if (!dst && !m2m_ctx->cap_q_ctx.buffered) {
dprintk("No output buffers available\n");
- goto cap_unlock;
+ goto job_unlock;
+ }
+
+ if (src && dst &&
+ dst->is_held && dst->vb2_buf.copied_timestamp &&
+ dst->vb2_buf.timestamp != src->vb2_buf.timestamp) {
+ dst->is_held = false;
+ v4l2_m2m_dst_buf_remove(m2m_ctx);
+ v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE);
+ dst = v4l2_m2m_next_dst_buf(m2m_ctx);
+
+ if (!dst && !m2m_ctx->cap_q_ctx.buffered) {
+ dprintk("No output buffers available after returning held buffer\n");
+ goto job_unlock;
+ }
}
- spin_unlock_irqrestore(&m2m_ctx->cap_q_ctx.rdy_spinlock, flags_cap);
- spin_unlock_irqrestore(&m2m_ctx->out_q_ctx.rdy_spinlock, flags_out);
if (m2m_dev->m2m_ops->job_ready
&& (!m2m_dev->m2m_ops->job_ready(m2m_ctx->priv))) {
@@ -331,13 +342,6 @@ static void __v4l2_m2m_try_queue(struct v4l2_m2m_dev *m2m_dev,
list_add_tail(&m2m_ctx->queue, &m2m_dev->job_queue);
m2m_ctx->job_flags |= TRANS_QUEUED;
- spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job);
- return;
-
-cap_unlock:
- spin_unlock_irqrestore(&m2m_ctx->cap_q_ctx.rdy_spinlock, flags_cap);
-out_unlock:
- spin_unlock_irqrestore(&m2m_ctx->out_q_ctx.rdy_spinlock, flags_out);
job_unlock:
spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job);
}
@@ -412,37 +416,97 @@ static void v4l2_m2m_cancel_job(struct v4l2_m2m_ctx *m2m_ctx)
}
}
-void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev,
- struct v4l2_m2m_ctx *m2m_ctx)
+/*
+ * Schedule the next job, called from v4l2_m2m_job_finish() or
+ * v4l2_m2m_buf_done_and_job_finish().
+ */
+static void v4l2_m2m_schedule_next_job(struct v4l2_m2m_dev *m2m_dev,
+ struct v4l2_m2m_ctx *m2m_ctx)
{
- unsigned long flags;
+ /*
+ * This instance might have more buffers ready, but since we do not
+ * allow more than one job on the job_queue per instance, each has
+ * to be scheduled separately after the previous one finishes.
+ */
+ __v4l2_m2m_try_queue(m2m_dev, m2m_ctx);
- spin_lock_irqsave(&m2m_dev->job_spinlock, flags);
+ /*
+ * We might be running in atomic context,
+ * but the job must be run in non-atomic context.
+ */
+ schedule_work(&m2m_dev->job_work);
+}
+
+/*
+ * Assumes job_spinlock is held, called from v4l2_m2m_job_finish() or
+ * v4l2_m2m_buf_done_and_job_finish().
+ */
+static bool _v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev,
+ struct v4l2_m2m_ctx *m2m_ctx)
+{
if (!m2m_dev->curr_ctx || m2m_dev->curr_ctx != m2m_ctx) {
- spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
dprintk("Called by an instance not currently running\n");
- return;
+ return false;
}
list_del(&m2m_dev->curr_ctx->queue);
m2m_dev->curr_ctx->job_flags &= ~(TRANS_QUEUED | TRANS_RUNNING);
wake_up(&m2m_dev->curr_ctx->finished);
m2m_dev->curr_ctx = NULL;
+ return true;
+}
- spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
-
- /* This instance might have more buffers ready, but since we do not
- * allow more than one job on the job_queue per instance, each has
- * to be scheduled separately after the previous one finishes. */
- __v4l2_m2m_try_queue(m2m_dev, m2m_ctx);
+void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev,
+ struct v4l2_m2m_ctx *m2m_ctx)
+{
+ unsigned long flags;
+ bool schedule_next;
- /* We might be running in atomic context,
- * but the job must be run in non-atomic context.
+ /*
+ * This function should not be used for drivers that support
+ * holding capture buffers. Those should use
+ * v4l2_m2m_buf_done_and_job_finish() instead.
*/
- schedule_work(&m2m_dev->job_work);
+ WARN_ON(m2m_ctx->cap_q_ctx.q.subsystem_flags &
+ VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF);
+ spin_lock_irqsave(&m2m_dev->job_spinlock, flags);
+ schedule_next = _v4l2_m2m_job_finish(m2m_dev, m2m_ctx);
+ spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
+
+ if (schedule_next)
+ v4l2_m2m_schedule_next_job(m2m_dev, m2m_ctx);
}
EXPORT_SYMBOL(v4l2_m2m_job_finish);
+void v4l2_m2m_buf_done_and_job_finish(struct v4l2_m2m_dev *m2m_dev,
+ struct v4l2_m2m_ctx *m2m_ctx,
+ enum vb2_buffer_state state)
+{
+ struct vb2_v4l2_buffer *src_buf, *dst_buf;
+ bool schedule_next = false;
+ unsigned long flags;
+
+ spin_lock_irqsave(&m2m_dev->job_spinlock, flags);
+ src_buf = v4l2_m2m_src_buf_remove(m2m_ctx);
+ dst_buf = v4l2_m2m_next_dst_buf(m2m_ctx);
+
+ if (WARN_ON(!src_buf || !dst_buf))
+ goto unlock;
+ v4l2_m2m_buf_done(src_buf, state);
+ dst_buf->is_held = src_buf->flags & V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF;
+ if (!dst_buf->is_held) {
+ v4l2_m2m_dst_buf_remove(m2m_ctx);
+ v4l2_m2m_buf_done(dst_buf, state);
+ }
+ schedule_next = _v4l2_m2m_job_finish(m2m_dev, m2m_ctx);
+unlock:
+ spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
+
+ if (schedule_next)
+ v4l2_m2m_schedule_next_job(m2m_dev, m2m_ctx);
+}
+EXPORT_SYMBOL(v4l2_m2m_buf_done_and_job_finish);
+
int v4l2_m2m_reqbufs(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
struct v4l2_requestbuffers *reqbufs)
{
diff --git a/include/media/v4l2-mem2mem.h b/include/media/v4l2-mem2mem.h
index 0b9c3a287061..229d9f5d4370 100644
--- a/include/media/v4l2-mem2mem.h
+++ b/include/media/v4l2-mem2mem.h
@@ -21,7 +21,8 @@
* callback.
* The job does NOT have to end before this callback returns
* (and it will be the usual case). When the job finishes,
- * v4l2_m2m_job_finish() has to be called.
+ * v4l2_m2m_job_finish() or v4l2_m2m_buf_done_and_job_finish()
+ * has to be called.
* @job_ready: optional. Should return 0 if the driver does not have a job
* fully prepared to run yet (i.e. it will not be able to finish a
* transaction without sleeping). If not provided, it will be
@@ -33,7 +34,8 @@
* stop the device safely; e.g. in the next interrupt handler),
* even if the transaction would not have been finished by then.
* After the driver performs the necessary steps, it has to call
- * v4l2_m2m_job_finish() (as if the transaction ended normally).
+ * v4l2_m2m_job_finish() or v4l2_m2m_buf_done_and_job_finish() as
+ * if the transaction ended normally.
* This function does not have to (and will usually not) wait
* until the device enters a state when it can be stopped.
*/
@@ -173,6 +175,33 @@ void v4l2_m2m_try_schedule(struct v4l2_m2m_ctx *m2m_ctx);
void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev,
struct v4l2_m2m_ctx *m2m_ctx);
+/**
+ * v4l2_m2m_buf_done_and_job_finish() - return source/destination buffers with
+ * state and inform the framework that a job has been finished and have it
+ * clean up
+ *
+ * @m2m_dev: opaque pointer to the internal data to handle M2M context
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ * @state: vb2 buffer state passed to v4l2_m2m_buf_done().
+ *
+ * Drivers that set V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF must use this
+ * function instead of job_finish() to take held buffers into account. It is
+ * optional for other drivers.
+ *
+ * This function removes the source buffer from the ready list and returns
+ * it with the given state. The same is done for the destination buffer, unless
+ * it is marked 'held'. In that case the buffer is kept on the ready list.
+ *
+ * After that the job is finished (see job_finish()).
+ *
+ * This allows for multiple output buffers to be used to fill in a single
+ * capture buffer. This is typically used by stateless decoders where
+ * multiple e.g. H.264 slices contribute to a single decoded frame.
+ */
+void v4l2_m2m_buf_done_and_job_finish(struct v4l2_m2m_dev *m2m_dev,
+ struct v4l2_m2m_ctx *m2m_ctx,
+ enum vb2_buffer_state state);
+
static inline void
v4l2_m2m_buf_done(struct vb2_v4l2_buffer *buf, enum vb2_buffer_state state)
{
--
2.23.0

View file

@ -0,0 +1,101 @@
From bef41d93aac64b54c3008ca6170bec54f85784f5 Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@siol.net>
Date: Fri, 11 Oct 2019 06:32:43 -0300
Subject: [PATCH] media: v4l2-mem2mem: add stateless_(try_)decoder_cmd ioctl
helpers
These helpers are used by stateless codecs when they support multiple
slices per frame and hold capture buffer flag is set. It's expected that
all such codecs will use this code.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Co-developed-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
drivers/media/v4l2-core/v4l2-mem2mem.c | 53 ++++++++++++++++++++++++++
include/media/v4l2-mem2mem.h | 4 ++
2 files changed, 57 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c
index 79c3656f24f7..b46d2c388349 100644
--- a/drivers/media/v4l2-core/v4l2-mem2mem.c
+++ b/drivers/media/v4l2-core/v4l2-mem2mem.c
@@ -1218,6 +1218,59 @@ int v4l2_m2m_ioctl_try_decoder_cmd(struct file *file, void *fh,
}
EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_try_decoder_cmd);
+int v4l2_m2m_ioctl_stateless_try_decoder_cmd(struct file *file, void *fh,
+ struct v4l2_decoder_cmd *dc)
+{
+ if (dc->cmd != V4L2_DEC_CMD_FLUSH)
+ return -EINVAL;
+
+ dc->flags = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_stateless_try_decoder_cmd);
+
+int v4l2_m2m_ioctl_stateless_decoder_cmd(struct file *file, void *priv,
+ struct v4l2_decoder_cmd *dc)
+{
+ struct v4l2_fh *fh = file->private_data;
+ struct vb2_v4l2_buffer *out_vb, *cap_vb;
+ struct v4l2_m2m_dev *m2m_dev = fh->m2m_ctx->m2m_dev;
+ unsigned long flags;
+ int ret;
+
+ ret = v4l2_m2m_ioctl_stateless_try_decoder_cmd(file, priv, dc);
+ if (ret < 0)
+ return ret;
+
+ spin_lock_irqsave(&m2m_dev->job_spinlock, flags);
+ out_vb = v4l2_m2m_last_src_buf(fh->m2m_ctx);
+ cap_vb = v4l2_m2m_last_dst_buf(fh->m2m_ctx);
+
+ /*
+ * If there is an out buffer pending, then clear any HOLD flag.
+ *
+ * By clearing this flag we ensure that when this output
+ * buffer is processed any held capture buffer will be released.
+ */
+ if (out_vb) {
+ out_vb->flags &= ~V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF;
+ } else if (cap_vb && cap_vb->is_held) {
+ /*
+ * If there were no output buffers, but there is a
+ * capture buffer that is held, then release that
+ * buffer.
+ */
+ cap_vb->is_held = false;
+ v4l2_m2m_dst_buf_remove(fh->m2m_ctx);
+ v4l2_m2m_buf_done(cap_vb, VB2_BUF_STATE_DONE);
+ }
+ spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_stateless_decoder_cmd);
+
/*
* v4l2_file_operations helpers. It is assumed here same lock is used
* for the output and the capture buffer queue.
diff --git a/include/media/v4l2-mem2mem.h b/include/media/v4l2-mem2mem.h
index 229d9f5d4370..3d9e48ed8817 100644
--- a/include/media/v4l2-mem2mem.h
+++ b/include/media/v4l2-mem2mem.h
@@ -701,6 +701,10 @@ int v4l2_m2m_ioctl_try_encoder_cmd(struct file *file, void *fh,
struct v4l2_encoder_cmd *ec);
int v4l2_m2m_ioctl_try_decoder_cmd(struct file *file, void *fh,
struct v4l2_decoder_cmd *dc);
+int v4l2_m2m_ioctl_stateless_try_decoder_cmd(struct file *file, void *fh,
+ struct v4l2_decoder_cmd *dc);
+int v4l2_m2m_ioctl_stateless_decoder_cmd(struct file *file, void *priv,
+ struct v4l2_decoder_cmd *dc);
int v4l2_m2m_fop_mmap(struct file *file, struct vm_area_struct *vma);
__poll_t v4l2_m2m_fop_poll(struct file *file, poll_table *wait);
--
2.23.0

View file

@ -0,0 +1,74 @@
From f07602ac388723233e9e3c5a05b54baf34e0a3e9 Mon Sep 17 00:00:00 2001
From: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Date: Fri, 11 Oct 2019 06:32:44 -0300
Subject: [PATCH] media: v4l2-mem2mem: add new_frame detection
Drivers that support VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF
typically want to know if a new frame is started (i.e. the first
slice is about to be processed). Add a new_frame bool to v4l2_m2m_ctx
and set it accordingly.
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
drivers/media/v4l2-core/v4l2-mem2mem.c | 11 +++++++++--
include/media/v4l2-mem2mem.h | 7 +++++++
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c
index b46d2c388349..db07ef3bf3d0 100644
--- a/drivers/media/v4l2-core/v4l2-mem2mem.c
+++ b/drivers/media/v4l2-core/v4l2-mem2mem.c
@@ -319,8 +319,10 @@ static void __v4l2_m2m_try_queue(struct v4l2_m2m_dev *m2m_dev,
goto job_unlock;
}
- if (src && dst &&
- dst->is_held && dst->vb2_buf.copied_timestamp &&
+ m2m_ctx->new_frame = true;
+
+ if (src && dst && dst->is_held &&
+ dst->vb2_buf.copied_timestamp &&
dst->vb2_buf.timestamp != src->vb2_buf.timestamp) {
dst->is_held = false;
v4l2_m2m_dst_buf_remove(m2m_ctx);
@@ -333,6 +335,11 @@ static void __v4l2_m2m_try_queue(struct v4l2_m2m_dev *m2m_dev,
}
}
+ if (src && dst && (m2m_ctx->cap_q_ctx.q.subsystem_flags &
+ VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF))
+ m2m_ctx->new_frame = !dst->vb2_buf.copied_timestamp ||
+ dst->vb2_buf.timestamp != src->vb2_buf.timestamp;
+
if (m2m_dev->m2m_ops->job_ready
&& (!m2m_dev->m2m_ops->job_ready(m2m_ctx->priv))) {
dprintk("Driver not ready\n");
diff --git a/include/media/v4l2-mem2mem.h b/include/media/v4l2-mem2mem.h
index 3d9e48ed8817..1d85e24791e4 100644
--- a/include/media/v4l2-mem2mem.h
+++ b/include/media/v4l2-mem2mem.h
@@ -75,6 +75,11 @@ struct v4l2_m2m_queue_ctx {
* struct v4l2_m2m_ctx - Memory to memory context structure
*
* @q_lock: struct &mutex lock
+ * @new_frame: valid in the device_run callback: if true, then this
+ * starts a new frame; if false, then this is a new slice
+ * for an existing frame. This is always true unless
+ * V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF is set, which
+ * indicates slicing support.
* @m2m_dev: opaque pointer to the internal data to handle M2M context
* @cap_q_ctx: Capture (output to memory) queue context
* @out_q_ctx: Output (input from memory) queue context
@@ -91,6 +96,8 @@ struct v4l2_m2m_ctx {
/* optional cap/out vb2 queues lock */
struct mutex *q_lock;
+ bool new_frame;
+
/* internal use only */
struct v4l2_m2m_dev *m2m_dev;
--
2.23.0

View file

@ -0,0 +1,199 @@
From 137272cdf7cc5be835f44216e6003769d1638480 Mon Sep 17 00:00:00 2001
From: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Date: Fri, 11 Oct 2019 06:32:40 -0300
Subject: [PATCH] media: vb2: add V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF
This patch adds support for the V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF
flag.
It also adds a new V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF
capability.
Drivers should set vb2_queue->subsystem_flags to
VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF to indicate support
for this flag.
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
Documentation/media/uapi/v4l/buffer.rst | 13 +++++++++++++
Documentation/media/uapi/v4l/vidioc-reqbufs.rst | 6 ++++++
drivers/media/common/videobuf2/videobuf2-v4l2.c | 12 ++++++++++--
include/media/videobuf2-core.h | 3 +++
include/media/videobuf2-v4l2.h | 5 +++++
include/uapi/linux/videodev2.h | 13 ++++++++-----
6 files changed, 45 insertions(+), 7 deletions(-)
diff --git a/Documentation/media/uapi/v4l/buffer.rst b/Documentation/media/uapi/v4l/buffer.rst
index 1cbd9cde57f3..9149b57728e5 100644
--- a/Documentation/media/uapi/v4l/buffer.rst
+++ b/Documentation/media/uapi/v4l/buffer.rst
@@ -607,6 +607,19 @@ Buffer Flags
applications shall use this flag for output buffers if the data in
this buffer has not been created by the CPU but by some
DMA-capable unit, in which case caches have not been used.
+ * .. _`V4L2-BUF-FLAG-M2M-HOLD-CAPTURE-BUF`:
+
+ - ``V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF``
+ - 0x00000200
+ - Only valid if ``V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF`` is
+ set. It is typically used with stateless decoders where multiple
+ output buffers each decode to a slice of the decoded frame.
+ Applications can set this flag when queueing the output buffer
+ to prevent the driver from dequeueing the capture buffer after
+ the output buffer has been decoded (i.e. the capture buffer is
+ 'held'). If the timestamp of this output buffer differs from that
+ of the previous output buffer, then that indicates the start of a
+ new frame and the previously held capture buffer is dequeued.
* .. _`V4L2-BUF-FLAG-LAST`:
- ``V4L2_BUF_FLAG_LAST``
diff --git a/Documentation/media/uapi/v4l/vidioc-reqbufs.rst b/Documentation/media/uapi/v4l/vidioc-reqbufs.rst
index d7faef10e39b..d0c643db477a 100644
--- a/Documentation/media/uapi/v4l/vidioc-reqbufs.rst
+++ b/Documentation/media/uapi/v4l/vidioc-reqbufs.rst
@@ -125,6 +125,7 @@ aborting or finishing any DMA in progress, an implicit
.. _V4L2-BUF-CAP-SUPPORTS-DMABUF:
.. _V4L2-BUF-CAP-SUPPORTS-REQUESTS:
.. _V4L2-BUF-CAP-SUPPORTS-ORPHANED-BUFS:
+.. _V4L2-BUF-CAP-SUPPORTS-M2M-HOLD-CAPTURE-BUF:
.. cssclass:: longtable
@@ -150,6 +151,11 @@ aborting or finishing any DMA in progress, an implicit
- The kernel allows calling :ref:`VIDIOC_REQBUFS` while buffers are still
mapped or exported via DMABUF. These orphaned buffers will be freed
when they are unmapped or when the exported DMABUF fds are closed.
+ * - ``V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF``
+ - 0x00000020
+ - Only valid for stateless decoders. If set, then userspace can set the
+ ``V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF`` flag to hold off on returning the
+ capture buffer until the OUTPUT timestamp changes.
Return Value
============
diff --git a/drivers/media/common/videobuf2/videobuf2-v4l2.c b/drivers/media/common/videobuf2/videobuf2-v4l2.c
index 5a9ba3846f0a..e652f4318284 100644
--- a/drivers/media/common/videobuf2/videobuf2-v4l2.c
+++ b/drivers/media/common/videobuf2/videobuf2-v4l2.c
@@ -49,8 +49,11 @@ module_param(debug, int, 0644);
V4L2_BUF_FLAG_REQUEST_FD | \
V4L2_BUF_FLAG_TIMESTAMP_MASK)
/* Output buffer flags that should be passed on to the driver */
-#define V4L2_BUFFER_OUT_FLAGS (V4L2_BUF_FLAG_PFRAME | V4L2_BUF_FLAG_BFRAME | \
- V4L2_BUF_FLAG_KEYFRAME | V4L2_BUF_FLAG_TIMECODE)
+#define V4L2_BUFFER_OUT_FLAGS (V4L2_BUF_FLAG_PFRAME | \
+ V4L2_BUF_FLAG_BFRAME | \
+ V4L2_BUF_FLAG_KEYFRAME | \
+ V4L2_BUF_FLAG_TIMECODE | \
+ V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF)
/*
* __verify_planes_array() - verify that the planes array passed in struct
@@ -194,6 +197,7 @@ static int vb2_fill_vb2_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b
}
vbuf->sequence = 0;
vbuf->request_fd = -1;
+ vbuf->is_held = false;
if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) {
switch (b->memory) {
@@ -321,6 +325,8 @@ static int vb2_fill_vb2_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b
*/
vbuf->flags &= ~V4L2_BUF_FLAG_TIMECODE;
vbuf->field = b->field;
+ if (!(q->subsystem_flags & VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF))
+ vbuf->flags &= ~V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF;
} else {
/* Zero any output buffer flags as this is a capture buffer */
vbuf->flags &= ~V4L2_BUFFER_OUT_FLAGS;
@@ -654,6 +660,8 @@ static void fill_buf_caps(struct vb2_queue *q, u32 *caps)
*caps |= V4L2_BUF_CAP_SUPPORTS_USERPTR;
if (q->io_modes & VB2_DMABUF)
*caps |= V4L2_BUF_CAP_SUPPORTS_DMABUF;
+ if (q->subsystem_flags & VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF)
+ *caps |= V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF;
#ifdef CONFIG_MEDIA_CONTROLLER_REQUEST_API
if (q->supports_requests)
*caps |= V4L2_BUF_CAP_SUPPORTS_REQUESTS;
diff --git a/include/media/videobuf2-core.h b/include/media/videobuf2-core.h
index 640aabe69450..a2b2208b02da 100644
--- a/include/media/videobuf2-core.h
+++ b/include/media/videobuf2-core.h
@@ -505,6 +505,8 @@ struct vb2_buf_ops {
* @buf_ops: callbacks to deliver buffer information.
* between user-space and kernel-space.
* @drv_priv: driver private data.
+ * @subsystem_flags: Flags specific to the subsystem (V4L2/DVB/etc.). Not used
+ * by the vb2 core.
* @buf_struct_size: size of the driver-specific buffer structure;
* "0" indicates the driver doesn't want to use a custom buffer
* structure type. for example, ``sizeof(struct vb2_v4l2_buffer)``
@@ -571,6 +573,7 @@ struct vb2_queue {
const struct vb2_buf_ops *buf_ops;
void *drv_priv;
+ u32 subsystem_flags;
unsigned int buf_struct_size;
u32 timestamp_flags;
gfp_t gfp_flags;
diff --git a/include/media/videobuf2-v4l2.h b/include/media/videobuf2-v4l2.h
index 8a10889dc2fd..59bf33a12648 100644
--- a/include/media/videobuf2-v4l2.h
+++ b/include/media/videobuf2-v4l2.h
@@ -33,6 +33,7 @@
* @timecode: frame timecode.
* @sequence: sequence count of this frame.
* @request_fd: the request_fd associated with this buffer
+ * @is_held: if true, then this capture buffer was held
* @planes: plane information (userptr/fd, length, bytesused, data_offset).
*
* Should contain enough information to be able to cover all the fields
@@ -46,9 +47,13 @@ struct vb2_v4l2_buffer {
struct v4l2_timecode timecode;
__u32 sequence;
__s32 request_fd;
+ bool is_held;
struct vb2_plane planes[VB2_MAX_PLANES];
};
+/* VB2 V4L2 flags as set in vb2_queue.subsystem_flags */
+#define VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF (1 << 0)
+
/*
* to_vb2_v4l2_buffer() - cast struct vb2_buffer * to struct vb2_v4l2_buffer *
*/
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index b3c0961b62a0..9f4e66affac4 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -920,11 +920,12 @@ struct v4l2_requestbuffers {
};
/* capabilities for struct v4l2_requestbuffers and v4l2_create_buffers */
-#define V4L2_BUF_CAP_SUPPORTS_MMAP (1 << 0)
-#define V4L2_BUF_CAP_SUPPORTS_USERPTR (1 << 1)
-#define V4L2_BUF_CAP_SUPPORTS_DMABUF (1 << 2)
-#define V4L2_BUF_CAP_SUPPORTS_REQUESTS (1 << 3)
-#define V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS (1 << 4)
+#define V4L2_BUF_CAP_SUPPORTS_MMAP (1 << 0)
+#define V4L2_BUF_CAP_SUPPORTS_USERPTR (1 << 1)
+#define V4L2_BUF_CAP_SUPPORTS_DMABUF (1 << 2)
+#define V4L2_BUF_CAP_SUPPORTS_REQUESTS (1 << 3)
+#define V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS (1 << 4)
+#define V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF (1 << 5)
/**
* struct v4l2_plane - plane info for multi-planar buffers
@@ -1046,6 +1047,8 @@ static inline __u64 v4l2_timeval_to_ns(const struct timeval *tv)
#define V4L2_BUF_FLAG_IN_REQUEST 0x00000080
/* timecode field is valid */
#define V4L2_BUF_FLAG_TIMECODE 0x00000100
+/* Don't return the capture buffer until OUTPUT timestamp changes */
+#define V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF 0x00000200
/* Buffer is prepared for queuing */
#define V4L2_BUF_FLAG_PREPARED 0x00000400
/* Cache handling flags */
--
2.23.0

View file

@ -0,0 +1,66 @@
From bac06ec36ea2012ff0daa9767d0f77bf9c6064ec Mon Sep 17 00:00:00 2001
From: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Date: Fri, 11 Oct 2019 06:32:42 -0300
Subject: [PATCH] media: videodev2.h: add V4L2_DEC_CMD_FLUSH
Add this new V4L2_DEC_CMD_FLUSH decoder command and document it.
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Alexandre Courbot <acourbot@chromium.org>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
Documentation/media/uapi/v4l/vidioc-decoder-cmd.rst | 10 +++++++++-
Documentation/media/videodev2.h.rst.exceptions | 1 +
include/uapi/linux/videodev2.h | 1 +
3 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/Documentation/media/uapi/v4l/vidioc-decoder-cmd.rst b/Documentation/media/uapi/v4l/vidioc-decoder-cmd.rst
index 57f0066f4cff..f1a504836f31 100644
--- a/Documentation/media/uapi/v4l/vidioc-decoder-cmd.rst
+++ b/Documentation/media/uapi/v4l/vidioc-decoder-cmd.rst
@@ -208,7 +208,15 @@ introduced in Linux 3.3. They are, however, mandatory for stateful mem2mem decod
been started yet, the driver will return an ``EPERM`` error code. When
the decoder is already running, this command does nothing. No
flags are defined for this command.
-
+ * - ``V4L2_DEC_CMD_FLUSH``
+ - 4
+ - Flush any held capture buffers. Only valid for stateless decoders.
+ This command is typically used when the application reached the
+ end of the stream and the last output buffer had the
+ ``V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF`` flag set. This would prevent
+ dequeueing the capture buffer containing the last decoded frame.
+ So this command can be used to explicitly flush that final decoded
+ frame. This command does nothing if there are no held capture buffers.
Return Value
============
diff --git a/Documentation/media/videodev2.h.rst.exceptions b/Documentation/media/videodev2.h.rst.exceptions
index b58e381bdf7b..c23e5ef30c78 100644
--- a/Documentation/media/videodev2.h.rst.exceptions
+++ b/Documentation/media/videodev2.h.rst.exceptions
@@ -435,6 +435,7 @@ replace define V4L2_DEC_CMD_START decoder-cmds
replace define V4L2_DEC_CMD_STOP decoder-cmds
replace define V4L2_DEC_CMD_PAUSE decoder-cmds
replace define V4L2_DEC_CMD_RESUME decoder-cmds
+replace define V4L2_DEC_CMD_FLUSH decoder-cmds
replace define V4L2_DEC_CMD_START_MUTE_AUDIO decoder-cmds
replace define V4L2_DEC_CMD_PAUSE_TO_BLACK decoder-cmds
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index 9f4e66affac4..d969842bbfe2 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -1984,6 +1984,7 @@ struct v4l2_encoder_cmd {
#define V4L2_DEC_CMD_STOP (1)
#define V4L2_DEC_CMD_PAUSE (2)
#define V4L2_DEC_CMD_RESUME (3)
+#define V4L2_DEC_CMD_FLUSH (4)
/* Flags for V4L2_DEC_CMD_START */
#define V4L2_DEC_CMD_START_MUTE_AUDIO (1 << 0)
--
2.23.0

View file

@ -0,0 +1,39 @@
From 7119ecef4e5ec51e45e6fbe1b5da4385fcae8ded Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@siol.net>
Date: Wed, 6 Nov 2019 08:02:53 +0100
Subject: [PATCH] media: v4l2-mem2mem: Fix hold buf flag checks
Hold buf flag is set on output queue, not capture. Fix that.
Fixes: f07602ac3887 ("media: v4l2-mem2mem: add new_frame detection")
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
---
drivers/media/v4l2-core/v4l2-mem2mem.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c
index db07ef3bf3d0..1afd9c6ad908 100644
--- a/drivers/media/v4l2-core/v4l2-mem2mem.c
+++ b/drivers/media/v4l2-core/v4l2-mem2mem.c
@@ -335,7 +335,7 @@ static void __v4l2_m2m_try_queue(struct v4l2_m2m_dev *m2m_dev,
}
}
- if (src && dst && (m2m_ctx->cap_q_ctx.q.subsystem_flags &
+ if (src && dst && (m2m_ctx->out_q_ctx.q.subsystem_flags &
VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF))
m2m_ctx->new_frame = !dst->vb2_buf.copied_timestamp ||
dst->vb2_buf.timestamp != src->vb2_buf.timestamp;
@@ -474,7 +474,7 @@ void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev,
* holding capture buffers. Those should use
* v4l2_m2m_buf_done_and_job_finish() instead.
*/
- WARN_ON(m2m_ctx->cap_q_ctx.q.subsystem_flags &
+ WARN_ON(m2m_ctx->out_q_ctx.q.subsystem_flags &
VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF);
spin_lock_irqsave(&m2m_dev->job_spinlock, flags);
schedule_next = _v4l2_m2m_job_finish(m2m_dev, m2m_ctx);
--
2.24.0

View file

@ -0,0 +1,47 @@
Add DPB entry flags to help indicate when a reference frame is a field picture
and how the DPB entry is referenced, top or bottom field or full frame.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
Documentation/media/uapi/v4l/ext-ctrls-codec.rst | 12 ++++++++++++
include/media/h264-ctrls.h | 4 ++++
2 files changed, 16 insertions(+)
diff --git a/Documentation/media/uapi/v4l/ext-ctrls-codec.rst b/Documentation/media/uapi/v4l/ext-ctrls-codec.rst
index bc5dd8e76567..eb6c32668ad7 100644
--- a/Documentation/media/uapi/v4l/ext-ctrls-codec.rst
+++ b/Documentation/media/uapi/v4l/ext-ctrls-codec.rst
@@ -2022,6 +2022,18 @@ enum v4l2_mpeg_video_h264_hierarchical_coding_type -
* - ``V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM``
- 0x00000004
- The DPB entry is a long term reference frame
+ * - ``V4L2_H264_DPB_ENTRY_FLAG_FIELD_PICTURE``
+ - 0x00000008
+ - The DPB entry is a field picture
+ * - ``V4L2_H264_DPB_ENTRY_FLAG_REF_TOP``
+ - 0x00000010
+ - The DPB entry is a top field reference
+ * - ``V4L2_H264_DPB_ENTRY_FLAG_REF_BOTTOM``
+ - 0x00000020
+ - The DPB entry is a bottom field reference
+ * - ``V4L2_H264_DPB_ENTRY_FLAG_REF_FRAME``
+ - 0x00000030
+ - The DPB entry is a reference frame
``V4L2_CID_MPEG_VIDEO_H264_DECODE_MODE (enum)``
Specifies the decoding mode to use. Currently exposes slice-based and
diff --git a/include/media/h264-ctrls.h b/include/media/h264-ctrls.h
index e877bf1d537c..76020ebd1e6c 100644
--- a/include/media/h264-ctrls.h
+++ b/include/media/h264-ctrls.h
@@ -185,6 +185,10 @@ struct v4l2_ctrl_h264_slice_params {
#define V4L2_H264_DPB_ENTRY_FLAG_VALID 0x01
#define V4L2_H264_DPB_ENTRY_FLAG_ACTIVE 0x02
#define V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM 0x04
+#define V4L2_H264_DPB_ENTRY_FLAG_FIELD_PICTURE 0x08
+#define V4L2_H264_DPB_ENTRY_FLAG_REF_TOP 0x10
+#define V4L2_H264_DPB_ENTRY_FLAG_REF_BOTTOM 0x20
+#define V4L2_H264_DPB_ENTRY_FLAG_REF_FRAME 0x30
struct v4l2_h264_dpb_entry {
__u64 reference_ts;

View file

@ -0,0 +1,145 @@
If a decoder needs a minimal buffer count to be queued on it's CAPTURE
queue, if a CMD_STOP is sent after a STREAMON but before all the required
buffers are queued, it should comply to the drain sequence and mark the
last queued buffer with V4L2_BUF_FLAG_LAST and mark it done to be dequeued.
This introduces a v4l2-mem2mem ioctl decoder command to track the command
sent to the decoder, and should be called by the affected drivers.
Suggested-by: Hans Verkuil <hverkuil@xs4all.nl>
Suggested-by: Maxime Jourdan <mjourdan@baylibre.com>
Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
---
drivers/media/v4l2-core/v4l2-mem2mem.c | 61 +++++++++++++++++++++++++-
include/media/v4l2-mem2mem.h | 14 ++++++
2 files changed, 73 insertions(+), 2 deletions(-)
diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c
index 1afd9c6ad908..b09616f9f102 100644
--- a/drivers/media/v4l2-core/v4l2-mem2mem.c
+++ b/drivers/media/v4l2-core/v4l2-mem2mem.c
@@ -556,6 +556,28 @@ int v4l2_m2m_querybuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
}
EXPORT_SYMBOL_GPL(v4l2_m2m_querybuf);
+static void v4l2_m2m_flag_last_buf_done(struct vb2_queue *q)
+{
+ struct vb2_buffer *vb;
+ struct vb2_v4l2_buffer *vbuf;
+ unsigned int i;
+
+ if (WARN_ON(q->is_output))
+ return;
+ if (list_empty(&q->queued_list))
+ return;
+
+ vb = list_first_entry(&q->queued_list, struct vb2_buffer, queued_entry);
+ for (i = 0; i < vb->num_planes; i++)
+ vb2_set_plane_payload(vb, i, 0);
+
+ vb->state = VB2_BUF_STATE_ACTIVE;
+ atomic_inc(&q->owned_by_drv_count);
+ vbuf = to_vb2_v4l2_buffer(vb);
+ vbuf->flags |= V4L2_BUF_FLAG_LAST;
+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+}
+
int v4l2_m2m_qbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
struct v4l2_buffer *buf)
{
@@ -570,11 +592,22 @@ int v4l2_m2m_qbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
__func__);
return -EPERM;
}
+
ret = vb2_qbuf(vq, vdev->v4l2_dev->mdev, buf);
- if (!ret && !(buf->flags & V4L2_BUF_FLAG_IN_REQUEST))
+ if (ret)
+ return ret;
+
+ /*
+ * If the capture queue isn't streaming and we were asked to
+ * stop, DONE the buffer instantly and flag as LAST
+ */
+ if (!V4L2_TYPE_IS_OUTPUT(vq->type) && m2m_ctx->stopped &&
+ vb2_is_streaming(vq) && !vb2_start_streaming_called(vq))
+ v4l2_m2m_flag_last_buf_done(vq);
+ else if ((buf->flags & V4L2_BUF_FLAG_IN_REQUEST))
v4l2_m2m_try_schedule(m2m_ctx);
- return ret;
+ return 0;
}
EXPORT_SYMBOL_GPL(v4l2_m2m_qbuf);
@@ -1225,6 +1258,30 @@ int v4l2_m2m_ioctl_try_decoder_cmd(struct file *file, void *fh,
}
EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_try_decoder_cmd);
+int v4l2_m2m_decoder_cmd(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
+ struct v4l2_decoder_cmd *dc)
+{
+ if (dc->cmd != V4L2_DEC_CMD_STOP && dc->cmd != V4L2_DEC_CMD_START)
+ return -EINVAL;
+
+ if (dc->cmd == V4L2_DEC_CMD_STOP)
+ m2m_ctx->stopped = true;
+ else
+ m2m_ctx->stopped = false;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_m2m_decoder_cmd);
+
+int v4l2_m2m_ioctl_decoder_cmd(struct file *file, void *priv,
+ struct v4l2_decoder_cmd *dc)
+{
+ struct v4l2_fh *fh = file->private_data;
+
+ return v4l2_m2m_decoder_cmd(file, fh->m2m_ctx, dc);
+}
+EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_decoder_cmd);
+
/*
* v4l2_file_operations helpers. It is assumed here same lock is used
* for the output and the capture buffer queue.
diff --git a/include/media/v4l2-mem2mem.h b/include/media/v4l2-mem2mem.h
index 1d85e24791e4..4c083cffdd86 100644
--- a/include/media/v4l2-mem2mem.h
+++ b/include/media/v4l2-mem2mem.h
@@ -98,6 +98,8 @@ struct v4l2_m2m_ctx {
bool new_frame;
+ bool stopped;
+
/* internal use only */
struct v4l2_m2m_dev *m2m_dev;
@@ -312,6 +314,16 @@ int v4l2_m2m_streamon(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
int v4l2_m2m_streamoff(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
enum v4l2_buf_type type);
+/**
+ * v4l2_m2m_decoder_cmd() - execute a decoder command
+ *
+ * @file: pointer to struct &file
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ * @dc: pointer to the decoder command
+ */
+int v4l2_m2m_decoder_cmd(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
+ struct v4l2_decoder_cmd *dc);
+
/**
* v4l2_m2m_poll() - poll replacement, for destination buffers only
*
@@ -704,6 +716,8 @@ int v4l2_m2m_ioctl_streamon(struct file *file, void *fh,
enum v4l2_buf_type type);
int v4l2_m2m_ioctl_streamoff(struct file *file, void *fh,
enum v4l2_buf_type type);
+int v4l2_m2m_ioctl_decoder_cmd(struct file *file, void *fh,
+ struct v4l2_decoder_cmd *dc);
int v4l2_m2m_ioctl_try_encoder_cmd(struct file *file, void *fh,
struct v4l2_encoder_cmd *ec);
int v4l2_m2m_ioctl_try_decoder_cmd(struct file *file, void *fh,

View file

@ -0,0 +1,366 @@
H6 PWM block is basically the same as A20 PWM, except that it also has
bus clock and reset line which needs to be handled accordingly.
Expand Allwinner PWM binding with H6 PWM specifics.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
.../bindings/pwm/allwinner,sun4i-a10-pwm.yaml | 36 ++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
index 0ac52f83a58c..deca5d81802f 100644
--- a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
+++ b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
@@ -30,13 +30,47 @@ properties:
- items:
- const: allwinner,sun50i-h5-pwm
- const: allwinner,sun5i-a13-pwm
+ - const: allwinner,sun50i-h6-pwm
reg:
maxItems: 1
- clocks:
+ # Even though it only applies to subschemas under the conditionals,
+ # not listing them here will trigger a warning because of the
+ # additionalsProperties set to false.
+ clocks: true
+ clock-names: true
+ resets:
maxItems: 1
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: allwinner,sun50i-h6-pwm
+
+ then:
+ properties:
+ clocks:
+ items:
+ - description: Module Clock
+ - description: Bus Clock
+
+ clock-names:
+ items:
+ - const: pwm
+ - const: bus
+
+ required:
+ - clock-names
+ - resets
+
+ else:
+ properties:
+ clocks:
+ maxItems: 1
+
required:
- "#pwm-cells"
- compatible
H6 PWM core needs deasserted reset line in order to work.
Add a quirk for it.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Acked-by: Maxime Ripard <maxime.ripard@bootlin.com>
---
drivers/pwm/pwm-sun4i.c | 27 +++++++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index de78c824bbfd..1b7be8fbde86 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -16,6 +16,7 @@
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
+#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>
@@ -72,12 +73,14 @@ static const u32 prescaler_table[] = {
struct sun4i_pwm_data {
bool has_prescaler_bypass;
+ bool has_reset;
unsigned int npwm;
};
struct sun4i_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
+ struct reset_control *rst;
void __iomem *base;
spinlock_t ctrl_lock;
const struct sun4i_pwm_data *data;
@@ -371,6 +374,14 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
if (IS_ERR(pwm->clk))
return PTR_ERR(pwm->clk);
+ if (pwm->data->has_reset) {
+ pwm->rst = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(pwm->rst))
+ return PTR_ERR(pwm->rst);
+
+ reset_control_deassert(pwm->rst);
+ }
+
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &sun4i_pwm_ops;
pwm->chip.base = -1;
@@ -383,19 +394,31 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
ret = pwmchip_add(&pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
- return ret;
+ goto err_pwm_add;
}
platform_set_drvdata(pdev, pwm);
return 0;
+
+err_pwm_add:
+ reset_control_assert(pwm->rst);
+
+ return ret;
}
static int sun4i_pwm_remove(struct platform_device *pdev)
{
struct sun4i_pwm_chip *pwm = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pwmchip_remove(&pwm->chip);
+ if (ret)
+ return ret;
- return pwmchip_remove(&pwm->chip);
+ reset_control_assert(pwm->rst);
+
+ return 0;
}
static struct platform_driver sun4i_pwm_driver = {
H6 PWM core needs bus clock to be enabled in order to work.
Add a quirk for it.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
drivers/pwm/pwm-sun4i.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 1b7be8fbde86..7d3ac3f2dc3f 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -72,6 +72,7 @@ static const u32 prescaler_table[] = {
};
struct sun4i_pwm_data {
+ bool has_bus_clock;
bool has_prescaler_bypass;
bool has_reset;
unsigned int npwm;
@@ -79,6 +80,7 @@ struct sun4i_pwm_data {
struct sun4i_pwm_chip {
struct pwm_chip chip;
+ struct clk *bus_clk;
struct clk *clk;
struct reset_control *rst;
void __iomem *base;
@@ -382,6 +384,16 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
reset_control_deassert(pwm->rst);
}
+ if (pwm->data->has_bus_clock) {
+ pwm->bus_clk = devm_clk_get(&pdev->dev, "bus");
+ if (IS_ERR(pwm->bus_clk)) {
+ ret = PTR_ERR(pwm->bus_clk);
+ goto err_bus;
+ }
+
+ clk_prepare_enable(pwm->bus_clk);
+ }
+
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &sun4i_pwm_ops;
pwm->chip.base = -1;
@@ -402,6 +414,8 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
return 0;
err_pwm_add:
+ clk_disable_unprepare(pwm->bus_clk);
+err_bus:
reset_control_assert(pwm->rst);
return ret;
@@ -416,6 +430,7 @@ static int sun4i_pwm_remove(struct platform_device *pdev)
if (ret)
return ret;
+ clk_disable_unprepare(pwm->bus_clk);
reset_control_assert(pwm->rst);
return 0;
Note that while H6 PWM has two channels, only first one is wired to
output pin. Second channel is used as a clock source to companion AC200
chip which is bundled into same package.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
drivers/pwm/pwm-sun4i.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 7d3ac3f2dc3f..9e0eca79ff88 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -331,6 +331,13 @@ static const struct sun4i_pwm_data sun4i_pwm_single_bypass = {
.npwm = 1,
};
+static const struct sun4i_pwm_data sun50i_pwm_dual_bypass_clk_rst = {
+ .has_bus_clock = true,
+ .has_prescaler_bypass = true,
+ .has_reset = true,
+ .npwm = 2,
+};
+
static const struct of_device_id sun4i_pwm_dt_ids[] = {
{
.compatible = "allwinner,sun4i-a10-pwm",
@@ -347,6 +354,9 @@ static const struct of_device_id sun4i_pwm_dt_ids[] = {
}, {
.compatible = "allwinner,sun8i-h3-pwm",
.data = &sun4i_pwm_single_bypass,
+ }, {
+ .compatible = "allwinner,sun50i-h6-pwm",
+ .data = &sun50i_pwm_dual_bypass_clk_rst,
}, {
/* sentinel */
},
PWM core has an option to bypass whole logic and output unchanged source
clock as PWM output. This is achieved by enabling bypass bit.
Note that when bypass is enabled, no other setting has any meaning, not
even enable bit.
This mode of operation is needed to achieve high enough frequency to
serve as clock source for AC200 chip, which is integrated into same
package as H6 SoC.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
drivers/pwm/pwm-sun4i.c | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 9e0eca79ff88..848cff26f385 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -120,6 +120,19 @@ static void sun4i_pwm_get_state(struct pwm_chip *chip,
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
+ /*
+ * PWM chapter in H6 manual has a diagram which explains that if bypass
+ * bit is set, no other setting has any meaning. Even more, experiment
+ * proved that also enable bit is ignored in this case.
+ */
+ if (val & BIT_CH(PWM_BYPASS, pwm->hwpwm)) {
+ state->period = DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC, clk_rate);
+ state->duty_cycle = state->period / 2;
+ state->polarity = PWM_POLARITY_NORMAL;
+ state->enabled = true;
+ return;
+ }
+
if ((PWM_REG_PRESCAL(val, pwm->hwpwm) == PWM_PRESCAL_MASK) &&
sun4i_pwm->data->has_prescaler_bypass)
prescaler = 1;
@@ -211,7 +224,8 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
{
struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip);
struct pwm_state cstate;
- u32 ctrl;
+ u32 ctrl, clk_rate;
+ bool bypass;
int ret;
unsigned int delay_us;
unsigned long now;
@@ -226,6 +240,16 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
}
}
+ /*
+ * Although it would make much more sense to check for bypass in
+ * sun4i_pwm_calculate(), value of bypass bit also depends on "enabled".
+ * Period is allowed to be rounded up or down.
+ */
+ clk_rate = clk_get_rate(sun4i_pwm->clk);
+ bypass = (state->period == NSEC_PER_SEC / clk_rate ||
+ state->period == DIV_ROUND_UP(NSEC_PER_SEC, clk_rate)) &&
+ state->enabled;
+
spin_lock(&sun4i_pwm->ctrl_lock);
ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
@@ -273,6 +297,11 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
}
+ if (bypass)
+ ctrl |= BIT_CH(PWM_BYPASS, pwm->hwpwm);
+ else
+ ctrl &= ~BIT_CH(PWM_BYPASS, pwm->hwpwm);
+
sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
spin_unlock(&sun4i_pwm->ctrl_lock);
Allwinner H6 PWM is similar to that in A20 except that it has additional
bus clock and reset line.
Note that first PWM channel is connected to output pin and second
channel is used internally, as a clock source to AC200 co-packaged chip.
This means that any combination of these two channels can be used and
thus it doesn't make sense to add pinctrl nodes at this point.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
index e8bed58e7246..c1abd805cfdc 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
@@ -229,6 +229,16 @@
status = "disabled";
};
+ pwm: pwm@300a000 {
+ compatible = "allwinner,sun50i-h6-pwm";
+ reg = <0x0300a000 0x400>;
+ clocks = <&osc24M>, <&ccu CLK_BUS_PWM>;
+ clock-names = "pwm", "bus";
+ resets = <&ccu RST_BUS_PWM>;
+ #pwm-cells = <3>;
+ status = "disabled";
+ };
+
pio: pinctrl@300b000 {
compatible = "allwinner,sun50i-h6-pinctrl";
reg = <0x0300b000 0x400>;

View file

@ -0,0 +1,569 @@
diff -Naur linux-5.3-rc2-old/drivers/mfd/ac200.c linux-5.3-rc2-old-new/drivers/mfd/ac200.c
--- linux-5.3-rc2-old/drivers/mfd/ac200.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-5.3-rc2-old-new/drivers/mfd/ac200.c 2019-08-03 15:24:25.929999997 +0200
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MFD core driver for X-Powers' AC200 IC
+ *
+ * The AC200 is a chip which is co-packaged with Allwinner H6 SoC and
+ * includes analog audio codec, analog TV encoder, ethernet PHY, eFuse
+ * and RTC.
+ *
+ * Copyright (c) 2019 Jernej Skrabec <jernej.skrabec@siol.net>
+ *
+ * Based on AC100 driver with following copyrights:
+ * Copyright (2016) Chen-Yu Tsai
+ */
+
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/ac200.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+struct ac200_dev {
+ struct clk *clk;
+ /*
+ * Lock is needed for serializing concurrent access to
+ * AC200 registers in order not to mess with register page.
+ */
+ struct mutex lock;
+ struct regmap *regmap;
+};
+
+/*
+ * Register values can't be cached because registers are divided
+ * into multiple pages.
+ */
+static const struct regmap_config ac200_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+};
+
+int ac200_reg_read(struct ac200_dev *ac200, u16 reg, u16 *value)
+{
+ unsigned int val;
+ int ret;
+
+ mutex_lock(&ac200->lock);
+ ret = regmap_write(ac200->regmap, AC200_TWI_REG_ADDR_H, reg >> 8);
+ if (ret)
+ goto read_reg_out;
+
+ ret = regmap_read(ac200->regmap, reg & 0xff, &val);
+ *value = val;
+
+read_reg_out:
+ mutex_unlock(&ac200->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ac200_reg_read);
+
+int ac200_reg_write(struct ac200_dev *ac200, u16 reg, u16 value)
+{
+ int ret;
+
+ mutex_lock(&ac200->lock);
+ ret = regmap_write(ac200->regmap, AC200_TWI_REG_ADDR_H, reg >> 8);
+ if (ret)
+ goto write_reg_out;
+
+ ret = regmap_write(ac200->regmap, reg & 0xff, value);
+
+write_reg_out:
+ mutex_unlock(&ac200->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ac200_reg_write);
+
+int ac200_reg_mod(struct ac200_dev *ac200, u16 reg, u16 mask, u16 value)
+{
+ unsigned int val;
+ int ret;
+
+ mutex_lock(&ac200->lock);
+ ret = regmap_write(ac200->regmap, AC200_TWI_REG_ADDR_H, reg >> 8);
+ if (ret)
+ goto mod_reg_out;
+
+ ret = regmap_read(ac200->regmap, reg & 0xff, &val);
+ if (ret)
+ goto mod_reg_out;
+
+ val &= ~mask;
+ val |= value;
+
+ ret = regmap_write(ac200->regmap, reg & 0xff, val);
+
+mod_reg_out:
+ mutex_unlock(&ac200->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ac200_reg_mod);
+
+static struct mfd_cell ac200_cells[] = {
+ {
+ .name = "ac200-codec",
+ .of_compatible = "x-powers,ac200-codec",
+ }, {
+ .name = "ac200-efuse",
+ .of_compatible = "x-powers,ac200-efuse",
+ }, {
+ .name = "ac200-ephy",
+ .of_compatible = "x-powers,ac200-ephy",
+ }, {
+ .name = "ac200-rtc",
+ .of_compatible = "x-powers,ac200-rtc",
+ }, {
+ .name = "ac200-tve",
+ .of_compatible = "x-powers,ac200-tve",
+ },
+};
+
+static int ac200_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &i2c->dev;
+ struct ac200_dev *ac200;
+ int ret;
+
+ ac200 = devm_kzalloc(dev, sizeof(*ac200), GFP_KERNEL);
+ if (!ac200)
+ return -ENOMEM;
+
+ mutex_init(&ac200->lock);
+ i2c_set_clientdata(i2c, ac200);
+
+ ac200->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(ac200->clk)) {
+ ret = PTR_ERR(ac200->clk);
+ dev_err(dev, "Can't obtain the clock: %d\n", ret);
+ return ret;
+ }
+
+ ac200->regmap = devm_regmap_init_i2c(i2c, &ac200_regmap_config);
+ if (IS_ERR(ac200->regmap)) {
+ ret = PTR_ERR(ac200->regmap);
+ dev_err(dev, "Regmap init failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(ac200->clk);
+ if (ret) {
+ dev_err(dev, "Can't enable the clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = ac200_reg_write(ac200, AC200_SYS_CONTROL, 0);
+ if (ret) {
+ dev_err(dev, "Can't put AC200 in reset: %d\n", ret);
+ goto err;
+ }
+
+ ret = ac200_reg_write(ac200, AC200_SYS_CONTROL, 1);
+ if (ret) {
+ dev_err(dev, "Can't put AC200 out of reset: %d\n", ret);
+ goto err;
+ }
+
+ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ac200_cells,
+ ARRAY_SIZE(ac200_cells), NULL, 0, NULL);
+ if (ret) {
+ dev_err(dev, "Failed to add MFD devices: %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ clk_disable_unprepare(ac200->clk);
+ return ret;
+}
+
+static int ac200_i2c_remove(struct i2c_client *i2c)
+{
+ struct ac200_dev *ac200 = i2c_get_clientdata(i2c);
+
+ ac200_reg_write(ac200, AC200_SYS_CONTROL, 0);
+
+ clk_disable_unprepare(ac200->clk);
+
+ return 0;
+}
+
+static const struct i2c_device_id ac200_ids[] = {
+ { "ac200", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, ac200_ids);
+
+static const struct of_device_id ac200_of_match[] = {
+ { .compatible = "x-powers,ac200" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ac200_of_match);
+
+static struct i2c_driver ac200_i2c_driver = {
+ .driver = {
+ .name = "ac200",
+ .of_match_table = of_match_ptr(ac200_of_match),
+ },
+ .probe = ac200_i2c_probe,
+ .remove = ac200_i2c_remove,
+ .id_table = ac200_ids,
+};
+module_i2c_driver(ac200_i2c_driver);
+
+MODULE_DESCRIPTION("MFD core driver for AC200");
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
+MODULE_LICENSE("GPL v2");
diff -Naur linux-5.3-rc2-old/drivers/mfd/Kconfig linux-5.3-rc2-old-new/drivers/mfd/Kconfig
--- linux-5.3-rc2-old/drivers/mfd/Kconfig 2019-07-28 21:47:02.000000000 +0200
+++ linux-5.3-rc2-old-new/drivers/mfd/Kconfig 2019-08-03 15:24:25.929999997 +0200
@@ -178,6 +178,15 @@
This driver include only the core APIs. You have to select individual
components like codecs or RTC under the corresponding menus.
+config MFD_AC200
+ tristate "X-Powers AC200"
+ select MFD_CORE
+ depends on I2C
+ help
+ If you say Y here you get support for the X-Powers AC200 IC.
+ This driver include only the core APIs. You have to select individual
+ components like Ethernet PHY or RTC under the corresponding menus.
+
config MFD_AXP20X
tristate
select MFD_CORE
diff -Naur linux-5.3-rc2-old/drivers/mfd/Makefile linux-5.3-rc2-old-new/drivers/mfd/Makefile
--- linux-5.3-rc2-old/drivers/mfd/Makefile 2019-07-28 21:47:02.000000000 +0200
+++ linux-5.3-rc2-old-new/drivers/mfd/Makefile 2019-08-03 15:24:25.929999997 +0200
@@ -143,6 +143,7 @@
obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o
obj-$(CONFIG_MFD_AC100) += ac100.o
+obj-$(CONFIG_MFD_AC200) += ac200.o
obj-$(CONFIG_MFD_AXP20X) += axp20x.o
obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o
obj-$(CONFIG_MFD_AXP20X_RSB) += axp20x-rsb.o
diff -Naur linux-5.3-rc2-old/drivers/net/phy/ac200.c linux-5.3-rc2-old-new/drivers/net/phy/ac200.c
--- linux-5.3-rc2-old/drivers/net/phy/ac200.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-5.3-rc2-old-new/drivers/net/phy/ac200.c 2019-08-03 15:24:25.929999997 +0200
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0+
+/**
+ * Driver for AC200 Ethernet PHY
+ *
+ * Copyright (c) 2019 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/ac200.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+
+#define AC200_EPHY_ID 0x00441400
+#define AC200_EPHY_ID_MASK 0x0ffffff0
+
+/* macros for system ephy control 0 register */
+#define AC200_EPHY_RESET_INVALID BIT(0)
+#define AC200_EPHY_SYSCLK_GATING BIT(1)
+
+/* macros for system ephy control 1 register */
+#define AC200_EPHY_E_EPHY_MII_IO_EN BIT(0)
+#define AC200_EPHY_E_LNK_LED_IO_EN BIT(1)
+#define AC200_EPHY_E_SPD_LED_IO_EN BIT(2)
+#define AC200_EPHY_E_DPX_LED_IO_EN BIT(3)
+
+/* macros for ephy control register */
+#define AC200_EPHY_SHUTDOWN BIT(0)
+#define AC200_EPHY_LED_POL BIT(1)
+#define AC200_EPHY_CLK_SEL BIT(2)
+#define AC200_EPHY_ADDR(x) (((x) & 0x1F) << 4)
+#define AC200_EPHY_XMII_SEL BIT(11)
+#define AC200_EPHY_CALIB(x) (((x) & 0xF) << 12)
+
+struct ac200_ephy_dev {
+ struct phy_driver *ephy;
+ struct ac200_dev *ac200;
+};
+
+static char *ac200_phy_name = "AC200 EPHY";
+
+static void disable_intelligent_ieee(struct phy_device *phydev)
+{
+ unsigned int value;
+
+ phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */
+ value = phy_read(phydev, 0x17);
+ value &= ~BIT(3); /* disable IEEE */
+ phy_write(phydev, 0x17, value);
+ phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */
+}
+
+static void disable_802_3az_ieee(struct phy_device *phydev)
+{
+ unsigned int value;
+
+ phy_write(phydev, 0xd, 0x7);
+ phy_write(phydev, 0xe, 0x3c);
+ phy_write(phydev, 0xd, BIT(14) | 0x7);
+ value = phy_read(phydev, 0xe);
+ value &= ~BIT(1);
+ phy_write(phydev, 0xd, 0x7);
+ phy_write(phydev, 0xe, 0x3c);
+ phy_write(phydev, 0xd, BIT(14) | 0x7);
+ phy_write(phydev, 0xe, value);
+
+ phy_write(phydev, 0x1f, 0x0200); /* switch to page 2 */
+ phy_write(phydev, 0x18, 0x0000);
+}
+
+static int ac200_ephy_config_init(struct phy_device *phydev)
+{
+ const struct ac200_ephy_dev *priv = phydev->drv->driver_data;
+ u16 value;
+
+ phy_write(phydev, 0x1f, 0x0100); /* Switch to Page 1 */
+ phy_write(phydev, 0x12, 0x4824); /* Disable APS */
+
+ phy_write(phydev, 0x1f, 0x0200); /* Switch to Page 2 */
+ phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */
+
+ phy_write(phydev, 0x1f, 0x0600); /* Switch to Page 6 */
+ phy_write(phydev, 0x14, 0x708f); /* PHYAFE TX optimization */
+ phy_write(phydev, 0x13, 0xF000); /* PHYAFE RX optimization */
+ phy_write(phydev, 0x15, 0x1530);
+
+ phy_write(phydev, 0x1f, 0x0800); /* Switch to Page 6 */
+ phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */
+
+ disable_intelligent_ieee(phydev); /* Disable Intelligent IEEE */
+ disable_802_3az_ieee(phydev); /* Disable 802.3az IEEE */
+ phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */
+
+ value = (phydev->interface == PHY_INTERFACE_MODE_RMII) ?
+ AC200_EPHY_XMII_SEL : 0;
+ ac200_reg_mod(priv->ac200, AC200_EPHY_CTL, AC200_EPHY_XMII_SEL, value);
+
+ /* FIXME: This is probably H6 specific */
+ value = phy_read(phydev, 0x13);
+ value |= BIT(12);
+ phy_write(phydev, 0x13, value);
+
+ return 0;
+
+}
+
+static const struct mdio_device_id __maybe_unused ac200_ephy_phy_tbl[] = {
+ { AC200_EPHY_ID, AC200_EPHY_ID_MASK },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(mdio, ac200_ephy_phy_tbl);
+
+static int ac200_ephy_probe(struct platform_device *pdev)
+{
+ struct ac200_dev *ac200 = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct ac200_ephy_dev *priv;
+ struct nvmem_cell *calcell;
+ struct phy_driver *ephy;
+ u16 *caldata, calib;
+ size_t callen;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ ephy = devm_kzalloc(dev, sizeof(*ephy), GFP_KERNEL);
+ if (!ephy)
+ return -ENOMEM;
+
+ calcell = devm_nvmem_cell_get(dev, "ephy_calib");
+ if (IS_ERR(calcell)) {
+ dev_err(dev, "Unable to find calibration data!\n");
+ return PTR_ERR(calcell);
+ }
+
+ caldata = nvmem_cell_read(calcell, &callen);
+ if (IS_ERR(caldata)) {
+ dev_err(dev, "Unable to read calibration data!\n");
+ return PTR_ERR(caldata);
+ }
+
+ if (callen != 2) {
+ dev_err(dev, "Calibration data has wrong length: 2 != %lu\n",
+ callen);
+ return -EINVAL;
+ }
+
+ calib = *caldata + 3;
+ kfree(caldata);
+
+ ephy->phy_id = AC200_EPHY_ID;
+ ephy->phy_id_mask = AC200_EPHY_ID_MASK;
+ ephy->name = ac200_phy_name;
+ ephy->driver_data = priv;
+ ephy->soft_reset = genphy_soft_reset;
+ ephy->config_init = ac200_ephy_config_init;
+ ephy->suspend = genphy_suspend;
+ ephy->resume = genphy_resume;
+
+ priv->ac200 = ac200;
+ priv->ephy = ephy;
+ platform_set_drvdata(pdev, priv);
+
+ ret = ac200_reg_write(ac200, AC200_SYS_EPHY_CTL0,
+ AC200_EPHY_RESET_INVALID |
+ AC200_EPHY_SYSCLK_GATING);
+ if (ret)
+ return ret;
+
+ ret = ac200_reg_write(ac200, AC200_SYS_EPHY_CTL1,
+ AC200_EPHY_E_EPHY_MII_IO_EN |
+ AC200_EPHY_E_LNK_LED_IO_EN |
+ AC200_EPHY_E_SPD_LED_IO_EN |
+ AC200_EPHY_E_DPX_LED_IO_EN);
+ if (ret)
+ return ret;
+
+ ret = ac200_reg_write(ac200, AC200_EPHY_CTL,
+ AC200_EPHY_LED_POL |
+ AC200_EPHY_CLK_SEL |
+ AC200_EPHY_ADDR(1) |
+ AC200_EPHY_CALIB(calib));
+ if (ret)
+ return ret;
+
+ ret = phy_driver_register(priv->ephy, THIS_MODULE);
+ if (ret) {
+ dev_err(dev, "Unable to register phy\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ac200_ephy_remove(struct platform_device *pdev)
+{
+ struct ac200_ephy_dev *priv = platform_get_drvdata(pdev);
+
+ phy_driver_unregister(priv->ephy);
+
+ ac200_reg_write(priv->ac200, AC200_EPHY_CTL,
+ AC200_EPHY_SHUTDOWN);
+ ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL1, 0);
+ ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL0, 0);
+
+ return 0;
+}
+
+static const struct of_device_id ac200_ephy_match[] = {
+ { .compatible = "x-powers,ac200-ephy" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ac200_ephy_match);
+
+static struct platform_driver ac200_ephy_driver = {
+ .probe = ac200_ephy_probe,
+ .remove = ac200_ephy_remove,
+ .driver = {
+ .name = "ac200-ephy",
+ .of_match_table = ac200_ephy_match,
+ },
+};
+module_platform_driver(ac200_ephy_driver);
+
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>>");
+MODULE_DESCRIPTION("AC200 Ethernet PHY driver");
+MODULE_LICENSE("GPL");
diff -Naur linux-5.3-rc2-old/drivers/net/phy/Kconfig linux-5.3-rc2-old-new/drivers/net/phy/Kconfig
--- linux-5.3-rc2-old/drivers/net/phy/Kconfig 2019-07-28 21:47:02.000000000 +0200
+++ linux-5.3-rc2-old-new/drivers/net/phy/Kconfig 2019-08-03 15:24:25.929999997 +0200
@@ -244,6 +244,13 @@
depends on HWMON || HWMON=n
select MDIO_I2C
+config AC200_PHY
+ tristate "AC200 EPHY"
+ depends on NVMEM
+ depends on OF
+ help
+ Fast ethernet PHY as found in X-Powers AC200 multi-function device.
+
config AMD_PHY
tristate "AMD PHYs"
---help---
diff -Naur linux-5.3-rc2-old/drivers/net/phy/Makefile linux-5.3-rc2-old-new/drivers/net/phy/Makefile
--- linux-5.3-rc2-old/drivers/net/phy/Makefile 2019-07-28 21:47:02.000000000 +0200
+++ linux-5.3-rc2-old-new/drivers/net/phy/Makefile 2019-08-03 15:24:25.929999997 +0200
@@ -46,6 +46,7 @@
sfp-obj-$(CONFIG_SFP) += sfp-bus.o
obj-y += $(sfp-obj-y) $(sfp-obj-m)
+obj-$(CONFIG_AC200_PHY) += ac200.o
obj-$(CONFIG_ADIN_PHY) += adin.o
obj-$(CONFIG_AMD_PHY) += amd.o
aquantia-objs += aquantia_main.o
diff -Naur linux-5.3-rc2-old/include/linux/mfd/ac200.h linux-5.3-rc2-old-new/include/linux/mfd/ac200.h
--- linux-5.3-rc2-old/include/linux/mfd/ac200.h 1970-01-01 01:00:00.000000000 +0100
+++ linux-5.3-rc2-old-new/include/linux/mfd/ac200.h 2019-08-03 15:24:25.929999997 +0200
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Functions and registers to access AC200 IC.
+ *
+ * Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#ifndef __LINUX_MFD_AC200_H
+#define __LINUX_MFD_AC200_H
+
+#include <linux/types.h>
+
+struct ac200_dev;
+
+/* interface registers (can be accessed from any page) */
+#define AC200_TWI_CHANGE_TO_RSB 0x3E
+#define AC200_TWI_PAD_DELAY 0xC4
+#define AC200_TWI_REG_ADDR_H 0xFE
+
+/* General registers */
+#define AC200_SYS_VERSION 0x0000
+#define AC200_SYS_CONTROL 0x0002
+#define AC200_SYS_IRQ_ENABLE 0x0004
+#define AC200_SYS_IRQ_STATUS 0x0006
+#define AC200_SYS_CLK_CTL 0x0008
+#define AC200_SYS_DLDO_OSC_CTL 0x000A
+#define AC200_SYS_PLL_CTL0 0x000C
+#define AC200_SYS_PLL_CTL1 0x000E
+#define AC200_SYS_AUDIO_CTL0 0x0010
+#define AC200_SYS_AUDIO_CTL1 0x0012
+#define AC200_SYS_EPHY_CTL0 0x0014
+#define AC200_SYS_EPHY_CTL1 0x0016
+#define AC200_SYS_TVE_CTL0 0x0018
+#define AC200_SYS_TVE_CTL1 0x001A
+
+/* EPHY registers */
+#define AC200_EPHY_CTL 0x6000
+#define AC200_EPHY_BIST 0x6002
+
+int ac200_reg_read(struct ac200_dev *ac200, u16 reg, u16 *value);
+int ac200_reg_write(struct ac200_dev *ac200, u16 reg, u16 value);
+int ac200_reg_mod(struct ac200_dev *ac200, u16 reg, u16 mask, u16 value);
+
+#endif /* __LINUX_MFD_AC200_H */

View file

@ -0,0 +1,79 @@
diff -Naur linux-5.3-rc6-old/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi linux-5.3-rc6-new/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
--- linux-5.3-rc6-old/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi 2019-09-02 12:17:46.116666674 +0200
+++ linux-5.3-rc6-new/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi 2019-09-02 12:31:05.253333344 +0200
@@ -17,6 +17,16 @@
#address-cells = <1>;
#size-cells = <1>;
+ ac200_pwm_clk: ac200_clk {
+ compatible = "pwm-clock";
+ #clock-cells = <0>;
+ clock-frequency = <24000000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pwm1_pin>;
+ pwms = <&pwm 1 42 0>;
+ status = "disabled";
+ };
+
cpus {
#address-cells = <1>;
#size-cells = <0>;
@@ -241,7 +250,10 @@
reg = <0x03006000 0x400>;
#address-cells = <1>;
#size-cells = <1>;
+ ephy_calib: ephy_calib@2c {
+ reg = <0x2c 0x2>;
+ };
speedbin_efuse: speed@1c {
reg = <0x1c 0x4>;
};
@@ -326,6 +341,16 @@
pins = "PH0", "PH1";
function = "uart0";
};
+
+ i2c3_pins: i2c3-pins {
+ pins = "PB17", "PB18";
+ function = "i2c3";
+ };
+
+ pwm1_pin: pwm1-pin {
+ pins = "PB19";
+ function = "pwm1";
+ };
};
gic: interrupt-controller@3021000 {
@@ -431,6 +456,30 @@
status = "disabled";
};
+ i2c3: i2c@5002c00 {
+ compatible = "allwinner,sun6i-a31-i2c";
+ reg = <0x05002c00 0x400>;
+ interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_I2C3>;
+ resets = <&ccu RST_BUS_I2C3>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c3_pins>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ac200: mfd@10 {
+ compatible = "x-powers,ac200";
+ reg = <0x10>;
+ clocks = <&ac200_pwm_clk>;
+
+ ac200_ephy: phy {
+ compatible = "x-powers,ac200-ephy";
+ nvmem-cells = <&ephy_calib>;
+ nvmem-cell-names = "ephy_calib";
+ };
+ };
+ };
+
emac: ethernet@5020000 {
compatible = "allwinner,sun50i-h6-emac",
"allwinner,sun50i-a64-emac";

View file

@ -0,0 +1,127 @@
From dec555256f2cb61ee94975727ec2d4a8d592ac92 Mon Sep 17 00:00:00 2001
From: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Date: Fri, 30 Aug 2019 06:26:23 -0300
Subject: [PATCH] media: cedrus: choose default pixelformat in try_fmt
If an unsupported pixelformat is passed to try_fmt, then pick
the first valid pixelformat instead. This is more standard V4L2
behavior.
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Reviewed-by: Jernej Skrabec <jernej.skrabec@siol.net>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
.../staging/media/sunxi/cedrus/cedrus_video.c | 46 ++++++++-----------
1 file changed, 20 insertions(+), 26 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
index eeee3efd247b..d69c9bcdb8e2 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
@@ -62,33 +62,30 @@ static inline struct cedrus_ctx *cedrus_file2ctx(struct file *file)
static struct cedrus_format *cedrus_find_format(u32 pixelformat, u32 directions,
unsigned int capabilities)
{
+ struct cedrus_format *first_valid_fmt = NULL;
struct cedrus_format *fmt;
unsigned int i;
for (i = 0; i < CEDRUS_FORMATS_COUNT; i++) {
fmt = &cedrus_formats[i];
- if (fmt->capabilities && (fmt->capabilities & capabilities) !=
- fmt->capabilities)
+ if ((fmt->capabilities & capabilities) != fmt->capabilities ||
+ !(fmt->directions & directions))
continue;
- if (fmt->pixelformat == pixelformat &&
- (fmt->directions & directions) != 0)
+ if (fmt->pixelformat == pixelformat)
break;
+
+ if (!first_valid_fmt)
+ first_valid_fmt = fmt;
}
if (i == CEDRUS_FORMATS_COUNT)
- return NULL;
+ return first_valid_fmt;
return &cedrus_formats[i];
}
-static bool cedrus_check_format(u32 pixelformat, u32 directions,
- unsigned int capabilities)
-{
- return cedrus_find_format(pixelformat, directions, capabilities);
-}
-
static void cedrus_prepare_format(struct v4l2_pix_format *pix_fmt)
{
unsigned int width = pix_fmt->width;
@@ -252,11 +249,14 @@ static int cedrus_try_fmt_vid_cap(struct file *file, void *priv,
struct cedrus_ctx *ctx = cedrus_file2ctx(file);
struct cedrus_dev *dev = ctx->dev;
struct v4l2_pix_format *pix_fmt = &f->fmt.pix;
+ struct cedrus_format *fmt =
+ cedrus_find_format(pix_fmt->pixelformat, CEDRUS_DECODE_DST,
+ dev->capabilities);
- if (!cedrus_check_format(pix_fmt->pixelformat, CEDRUS_DECODE_DST,
- dev->capabilities))
+ if (!fmt)
return -EINVAL;
+ pix_fmt->pixelformat = fmt->pixelformat;
cedrus_prepare_format(pix_fmt);
return 0;
@@ -268,15 +268,18 @@ static int cedrus_try_fmt_vid_out(struct file *file, void *priv,
struct cedrus_ctx *ctx = cedrus_file2ctx(file);
struct cedrus_dev *dev = ctx->dev;
struct v4l2_pix_format *pix_fmt = &f->fmt.pix;
+ struct cedrus_format *fmt =
+ cedrus_find_format(pix_fmt->pixelformat, CEDRUS_DECODE_SRC,
+ dev->capabilities);
- if (!cedrus_check_format(pix_fmt->pixelformat, CEDRUS_DECODE_SRC,
- dev->capabilities))
+ if (!fmt)
return -EINVAL;
/* Source image size has to be provided by userspace. */
if (pix_fmt->sizeimage == 0)
return -EINVAL;
+ pix_fmt->pixelformat = fmt->pixelformat;
cedrus_prepare_format(pix_fmt);
return 0;
@@ -364,21 +367,12 @@ static int cedrus_queue_setup(struct vb2_queue *vq, unsigned int *nbufs,
struct device *alloc_devs[])
{
struct cedrus_ctx *ctx = vb2_get_drv_priv(vq);
- struct cedrus_dev *dev = ctx->dev;
struct v4l2_pix_format *pix_fmt;
- u32 directions;
- if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
- directions = CEDRUS_DECODE_SRC;
+ if (V4L2_TYPE_IS_OUTPUT(vq->type))
pix_fmt = &ctx->src_fmt;
- } else {
- directions = CEDRUS_DECODE_DST;
+ else
pix_fmt = &ctx->dst_fmt;
- }
-
- if (!cedrus_check_format(pix_fmt->pixelformat, directions,
- dev->capabilities))
- return -EINVAL;
if (*nplanes) {
if (sizes[0] < pix_fmt->sizeimage)
--
2.23.0

View file

@ -0,0 +1,135 @@
From 965c71e8adcff315e16b58c00cd312598fc0222c Mon Sep 17 00:00:00 2001
From: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Date: Fri, 30 Aug 2019 06:26:24 -0300
Subject: [PATCH] media: cedrus: fix various format-related compliance issues
Initialize the context on open() with valid capture and output
formats. It is good practice to always have valid formats internally.
This solves one vb2 warning in the kernel log where the sizeimage
value of the output format was 0 when requesting buffers, which is
not allowed.
It also simplifies the g_fmt ioctl implementations since they no longer
have to check if a valid format was ever set.
cedrus_prepare_format() now also validates sizeimage for the output
formats, ensuring userspace can't set it to 0 since that would cause
the same vb2 warning.
Finally remove the sizeimage == 0 check in cedrus_try_fmt_vid_out()
since cedrus_prepare_format() will now adjust this value.
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Reviewed-by: Jernej Skrabec <jernej.skrabec@siol.net>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
drivers/staging/media/sunxi/cedrus/cedrus.c | 10 +++++++
.../staging/media/sunxi/cedrus/cedrus_video.c | 28 ++-----------------
.../staging/media/sunxi/cedrus/cedrus_video.h | 1 +
3 files changed, 14 insertions(+), 25 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.c b/drivers/staging/media/sunxi/cedrus/cedrus.c
index 3439f6ad6338..0cf637c8a1e3 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.c
@@ -241,6 +241,16 @@ static int cedrus_open(struct file *file)
ret = PTR_ERR(ctx->fh.m2m_ctx);
goto err_ctrls;
}
+ ctx->dst_fmt.pixelformat = V4L2_PIX_FMT_SUNXI_TILED_NV12;
+ cedrus_prepare_format(&ctx->dst_fmt);
+ ctx->src_fmt.pixelformat = V4L2_PIX_FMT_MPEG2_SLICE;
+ /*
+ * TILED_NV12 has more strict requirements, so copy the width and
+ * height to src_fmt to ensure that is matches the dst_fmt resolution.
+ */
+ ctx->src_fmt.width = ctx->dst_fmt.width;
+ ctx->src_fmt.height = ctx->dst_fmt.height;
+ cedrus_prepare_format(&ctx->src_fmt);
v4l2_fh_add(&ctx->fh);
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
index d69c9bcdb8e2..3ec3a2db790c 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
@@ -86,7 +86,7 @@ static struct cedrus_format *cedrus_find_format(u32 pixelformat, u32 directions,
return &cedrus_formats[i];
}
-static void cedrus_prepare_format(struct v4l2_pix_format *pix_fmt)
+void cedrus_prepare_format(struct v4l2_pix_format *pix_fmt)
{
unsigned int width = pix_fmt->width;
unsigned int height = pix_fmt->height;
@@ -104,7 +104,8 @@ static void cedrus_prepare_format(struct v4l2_pix_format *pix_fmt)
case V4L2_PIX_FMT_H264_SLICE:
/* Zero bytes per line for encoded source. */
bytesperline = 0;
-
+ /* Choose some minimum size since this can't be 0 */
+ sizeimage = max_t(u32, SZ_1K, sizeimage);
break;
case V4L2_PIX_FMT_SUNXI_TILED_NV12:
@@ -211,16 +212,7 @@ static int cedrus_g_fmt_vid_cap(struct file *file, void *priv,
{
struct cedrus_ctx *ctx = cedrus_file2ctx(file);
- /* Fall back to dummy default by lack of hardware configuration. */
- if (!ctx->dst_fmt.width || !ctx->dst_fmt.height) {
- f->fmt.pix.pixelformat = V4L2_PIX_FMT_SUNXI_TILED_NV12;
- cedrus_prepare_format(&f->fmt.pix);
-
- return 0;
- }
-
f->fmt.pix = ctx->dst_fmt;
-
return 0;
}
@@ -229,17 +221,7 @@ static int cedrus_g_fmt_vid_out(struct file *file, void *priv,
{
struct cedrus_ctx *ctx = cedrus_file2ctx(file);
- /* Fall back to dummy default by lack of hardware configuration. */
- if (!ctx->dst_fmt.width || !ctx->dst_fmt.height) {
- f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG2_SLICE;
- f->fmt.pix.sizeimage = SZ_1K;
- cedrus_prepare_format(&f->fmt.pix);
-
- return 0;
- }
-
f->fmt.pix = ctx->src_fmt;
-
return 0;
}
@@ -275,10 +257,6 @@ static int cedrus_try_fmt_vid_out(struct file *file, void *priv,
if (!fmt)
return -EINVAL;
- /* Source image size has to be provided by userspace. */
- if (pix_fmt->sizeimage == 0)
- return -EINVAL;
-
pix_fmt->pixelformat = fmt->pixelformat;
cedrus_prepare_format(pix_fmt);
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.h b/drivers/staging/media/sunxi/cedrus/cedrus_video.h
index 0e4f7a8cccf2..05050c0a0921 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_video.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.h
@@ -26,5 +26,6 @@ extern const struct v4l2_ioctl_ops cedrus_ioctl_ops;
int cedrus_queue_init(void *priv, struct vb2_queue *src_vq,
struct vb2_queue *dst_vq);
+void cedrus_prepare_format(struct v4l2_pix_format *pix_fmt);
#endif
--
2.23.0

View file

@ -0,0 +1,139 @@
From eabf10e5e3009e0c7e9a9b98a7f8299e690bcc55 Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@siol.net>
Date: Fri, 11 Oct 2019 06:32:45 -0300
Subject: [PATCH] media: cedrus: h264: Support multiple slices per frame
With recent changes, support for decoding multi-slice frames can be
easily added now.
Signal VPU if current slice is first in frame or not and add information
about first macroblock coordinates.
When frame contains multiple slices and driver works in slice mode, it's
more efficient to hold capture buffer in queue until all slices of a
same frame are decoded.
Add support for that to Cedrus driver by exposing and implementing
V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF capability.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
[hverkuil-cisco@xs4all.nl: rewritten to use v4l2_m2m_buf_done_and_job_finish]
[hverkuil-cisco@xs4all.nl: removed unnecessary (u32) cast]
[hverkuil-cisco@xs4all.nl: use new_frame v4l2_m2m_ctx bool]
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
drivers/staging/media/sunxi/cedrus/cedrus_h264.c | 12 +++++++++++-
drivers/staging/media/sunxi/cedrus/cedrus_hw.c | 16 ++--------------
.../staging/media/sunxi/cedrus/cedrus_video.c | 14 ++++++++++++++
3 files changed, 27 insertions(+), 15 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
index d6a782703c9b..cd85668f9c80 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
@@ -301,6 +301,8 @@ static void cedrus_set_params(struct cedrus_ctx *ctx,
dma_addr_t src_buf_addr;
u32 offset = slice->header_bit_size;
u32 len = (slice->size * 8) - offset;
+ unsigned int pic_width_in_mbs;
+ bool mbaff_pic;
u32 reg;
cedrus_write(dev, VE_H264_VLD_LEN, len);
@@ -370,12 +372,20 @@ static void cedrus_set_params(struct cedrus_ctx *ctx,
reg |= VE_H264_SPS_DIRECT_8X8_INFERENCE;
cedrus_write(dev, VE_H264_SPS, reg);
+ mbaff_pic = !(slice->flags & V4L2_H264_SLICE_FLAG_FIELD_PIC) &&
+ (sps->flags & V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD);
+ pic_width_in_mbs = sps->pic_width_in_mbs_minus1 + 1;
+
// slice parameters
reg = 0;
+ reg |= ((slice->first_mb_in_slice % pic_width_in_mbs) & 0xff) << 24;
+ reg |= (((slice->first_mb_in_slice / pic_width_in_mbs) *
+ (mbaff_pic + 1)) & 0xff) << 16;
reg |= decode->nal_ref_idc ? BIT(12) : 0;
reg |= (slice->slice_type & 0xf) << 8;
reg |= slice->cabac_init_idc & 0x3;
- reg |= VE_H264_SHS_FIRST_SLICE_IN_PIC;
+ if (ctx->fh.m2m_ctx->new_frame)
+ reg |= VE_H264_SHS_FIRST_SLICE_IN_PIC;
if (slice->flags & V4L2_H264_SLICE_FLAG_FIELD_PIC)
reg |= VE_H264_SHS_FIELD_PIC;
if (slice->flags & V4L2_H264_SLICE_FLAG_BOTTOM_FIELD)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
index a942cd9bed57..e7e18424bab1 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
@@ -103,7 +103,6 @@ static irqreturn_t cedrus_irq(int irq, void *data)
{
struct cedrus_dev *dev = data;
struct cedrus_ctx *ctx;
- struct vb2_v4l2_buffer *src_buf, *dst_buf;
enum vb2_buffer_state state;
enum cedrus_irq_status status;
@@ -121,24 +120,13 @@ static irqreturn_t cedrus_irq(int irq, void *data)
dev->dec_ops[ctx->current_codec]->irq_disable(ctx);
dev->dec_ops[ctx->current_codec]->irq_clear(ctx);
- src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
- dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
-
- if (!src_buf || !dst_buf) {
- v4l2_err(&dev->v4l2_dev,
- "Missing source and/or destination buffers\n");
- return IRQ_HANDLED;
- }
-
if (status == CEDRUS_IRQ_ERROR)
state = VB2_BUF_STATE_ERROR;
else
state = VB2_BUF_STATE_DONE;
- v4l2_m2m_buf_done(src_buf, state);
- v4l2_m2m_buf_done(dst_buf, state);
-
- v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx);
+ v4l2_m2m_buf_done_and_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx,
+ state);
return IRQ_HANDLED;
}
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
index 3ec3a2db790c..f745f66c4440 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
@@ -303,6 +303,17 @@ static int cedrus_s_fmt_vid_out(struct file *file, void *priv,
ctx->src_fmt = f->fmt.pix;
+ switch (ctx->src_fmt.pixelformat) {
+ case V4L2_PIX_FMT_H264_SLICE:
+ vq->subsystem_flags |=
+ VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF;
+ break;
+ default:
+ vq->subsystem_flags &=
+ ~VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF;
+ break;
+ }
+
/* Propagate colorspace information to capture. */
ctx->dst_fmt.colorspace = f->fmt.pix.colorspace;
ctx->dst_fmt.xfer_func = f->fmt.pix.xfer_func;
@@ -336,6 +347,9 @@ const struct v4l2_ioctl_ops cedrus_ioctl_ops = {
.vidioc_streamon = v4l2_m2m_ioctl_streamon,
.vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+ .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_stateless_try_decoder_cmd,
+ .vidioc_decoder_cmd = v4l2_m2m_ioctl_stateless_decoder_cmd,
+
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
--
2.23.0

View file

@ -0,0 +1,34 @@
From c3b32900fbf5178473c6b39260e891e19067edc2 Mon Sep 17 00:00:00 2001
From: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
Date: Tue, 22 Oct 2019 12:26:51 -0300
Subject: [PATCH] media: cedrus: Remove unnecessary parenthesis aroundDIV_ROUND_UP
DIV_ROUND_UP's first argument doesn't need to be wrapped in parenthesis
since that is already being taken care of in the macro's definition.
Signed-off-by: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
drivers/staging/media/sunxi/cedrus/cedrus_regs.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
index f9dd8cbf3458..21676a1797f1 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
@@ -101,9 +101,9 @@
#define VE_DEC_MPEG_PICCODEDSIZE (VE_ENGINE_DEC_MPEG + 0x08)
#define VE_DEC_MPEG_PICCODEDSIZE_WIDTH(w) \
- SHIFT_AND_MASK_BITS(DIV_ROUND_UP((w), 16), 15, 8)
+ SHIFT_AND_MASK_BITS(DIV_ROUND_UP(w, 16), 15, 8)
#define VE_DEC_MPEG_PICCODEDSIZE_HEIGHT(h) \
- SHIFT_AND_MASK_BITS(DIV_ROUND_UP((h), 16), 7, 0)
+ SHIFT_AND_MASK_BITS(DIV_ROUND_UP(h, 16), 7, 0)
#define VE_DEC_MPEG_PICBOUNDSIZE (VE_ENGINE_DEC_MPEG + 0x0c)
--
2.23.0

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,110 @@
From b4e33e09e7938513bfaba034731c7e84e73c6a5b Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@siol.net>
Date: Sat, 26 Oct 2019 09:27:51 +0200
Subject: [PATCH] media: cedrus: Fix decoding for some H264 videos
It seems that for some H264 videos at least one bitstream parsing
trigger must be called in order to be decoded correctly. There is no
explanation why this helps, but it was observed that two sample videos
with this fix are now decoded correctly and there is no regression with
others.
Acked-by: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
---
.../staging/media/sunxi/cedrus/cedrus_h264.c | 30 +++++++++++++++++--
.../staging/media/sunxi/cedrus/cedrus_regs.h | 3 ++
2 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
index cd85668f9c80..db336449c4f2 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
@@ -6,6 +6,7 @@
* Copyright (c) 2018 Bootlin
*/
+#include <linux/delay.h>
#include <linux/types.h>
#include <media/videobuf2-dma-contig.h>
@@ -289,6 +290,28 @@ static void cedrus_write_pred_weight_table(struct cedrus_ctx *ctx,
}
}
+/*
+ * It turns out that using VE_H264_VLD_OFFSET to skip bits is not reliable. In
+ * rare cases frame is not decoded correctly. However, setting offset to 0 and
+ * skipping appropriate amount of bits with flush bits trigger always works.
+ */
+static void cedrus_skip_bits(struct cedrus_dev *dev, int num)
+{
+ int count = 0;
+
+ while (count < num) {
+ int tmp = min(num - count, 32);
+
+ cedrus_write(dev, VE_H264_TRIGGER_TYPE,
+ VE_H264_TRIGGER_TYPE_FLUSH_BITS |
+ VE_H264_TRIGGER_TYPE_N_BITS(tmp));
+ while (cedrus_read(dev, VE_H264_STATUS) & VE_H264_STATUS_VLD_BUSY)
+ udelay(1);
+
+ count += tmp;
+ }
+}
+
static void cedrus_set_params(struct cedrus_ctx *ctx,
struct cedrus_run *run)
{
@@ -299,14 +322,13 @@ static void cedrus_set_params(struct cedrus_ctx *ctx,
struct vb2_buffer *src_buf = &run->src->vb2_buf;
struct cedrus_dev *dev = ctx->dev;
dma_addr_t src_buf_addr;
- u32 offset = slice->header_bit_size;
- u32 len = (slice->size * 8) - offset;
+ u32 len = slice->size * 8;
unsigned int pic_width_in_mbs;
bool mbaff_pic;
u32 reg;
cedrus_write(dev, VE_H264_VLD_LEN, len);
- cedrus_write(dev, VE_H264_VLD_OFFSET, offset);
+ cedrus_write(dev, VE_H264_VLD_OFFSET, 0);
src_buf_addr = vb2_dma_contig_plane_dma_addr(src_buf, 0);
cedrus_write(dev, VE_H264_VLD_END,
@@ -325,6 +347,8 @@ static void cedrus_set_params(struct cedrus_ctx *ctx,
cedrus_write(dev, VE_H264_TRIGGER_TYPE,
VE_H264_TRIGGER_TYPE_INIT_SWDEC);
+ cedrus_skip_bits(dev, slice->header_bit_size);
+
if (((pps->flags & V4L2_H264_PPS_FLAG_WEIGHTED_PRED) &&
(slice->slice_type == V4L2_H264_SLICE_TYPE_P ||
slice->slice_type == V4L2_H264_SLICE_TYPE_SP)) ||
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
index 6fc28d21a6c7..4275a307d282 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
@@ -541,13 +541,16 @@
VE_H264_CTRL_SLICE_DECODE_INT)
#define VE_H264_TRIGGER_TYPE 0x224
+#define VE_H264_TRIGGER_TYPE_N_BITS(x) (((x) & 0x3f) << 8)
#define VE_H264_TRIGGER_TYPE_AVC_SLICE_DECODE (8 << 0)
#define VE_H264_TRIGGER_TYPE_INIT_SWDEC (7 << 0)
+#define VE_H264_TRIGGER_TYPE_FLUSH_BITS (3 << 0)
#define VE_H264_STATUS 0x228
#define VE_H264_STATUS_VLD_DATA_REQ_INT VE_H264_CTRL_VLD_DATA_REQ_INT
#define VE_H264_STATUS_DECODE_ERR_INT VE_H264_CTRL_DECODE_ERR_INT
#define VE_H264_STATUS_SLICE_DECODE_INT VE_H264_CTRL_SLICE_DECODE_INT
+#define VE_H264_STATUS_VLD_BUSY BIT(8)
#define VE_H264_STATUS_INT_MASK VE_H264_CTRL_INT_MASK
--
2.24.0

View file

@ -0,0 +1,34 @@
From e2c02ba9bd0068b00628d7874d8a8d1eb5168177 Mon Sep 17 00:00:00 2001
From: Jonas Karlman <jonas@kwiboo.se>
Date: Tue, 29 Oct 2019 00:00:52 +0000
Subject: [PATCH] media: cedrus: Use correct H264 8x8 scaling list
Documentation now defines the expected order of scaling lists,
change to use correct indices.
Fixes: 6eb9b758e307 ("media: cedrus: Add H264 decoding support")
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
---
drivers/staging/media/sunxi/cedrus/cedrus_h264.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
index 7487f6ab7576..74e4c5e3894e 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
@@ -245,8 +245,8 @@ static void cedrus_write_scaling_lists(struct cedrus_ctx *ctx,
sizeof(scaling->scaling_list_8x8[0]));
cedrus_h264_write_sram(dev, CEDRUS_SRAM_H264_SCALING_LIST_8x8_1,
- scaling->scaling_list_8x8[3],
- sizeof(scaling->scaling_list_8x8[3]));
+ scaling->scaling_list_8x8[1],
+ sizeof(scaling->scaling_list_8x8[1]));
cedrus_h264_write_sram(dev, CEDRUS_SRAM_H264_SCALING_LIST_4x4,
scaling->scaling_list_4x4,
--
2.24.0

View file

@ -0,0 +1,118 @@
From 3aef46bd5bf24a845e05d2531ed61f53ee8c7797 Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@siol.net>
Date: Sun, 10 Nov 2019 07:30:01 +0100
Subject: [PATCH 1/3] media: cedrus: Properly signal size in mode register
Mode register also holds information if video width is bigger than 2048
and if it is equal to 4096.
Rework cedrus_engine_enable() to properly signal this properties.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Acked-by: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
---
drivers/staging/media/sunxi/cedrus/cedrus_h264.c | 2 +-
drivers/staging/media/sunxi/cedrus/cedrus_h265.c | 2 +-
drivers/staging/media/sunxi/cedrus/cedrus_hw.c | 9 +++++++--
drivers/staging/media/sunxi/cedrus/cedrus_hw.h | 2 +-
drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c | 2 +-
drivers/staging/media/sunxi/cedrus/cedrus_regs.h | 2 ++
6 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
index 74e4c5e3894e..8a09a08b1af2 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
@@ -485,7 +485,7 @@ static void cedrus_h264_setup(struct cedrus_ctx *ctx,
{
struct cedrus_dev *dev = ctx->dev;
- cedrus_engine_enable(dev, CEDRUS_CODEC_H264);
+ cedrus_engine_enable(ctx, CEDRUS_CODEC_H264);
cedrus_write(dev, VE_H264_SDROT_CTRL, 0);
cedrus_write(dev, VE_H264_EXTRA_BUFFER1,
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h265.c b/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
index 9bc921866f70..6945dc74e1d7 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
@@ -276,7 +276,7 @@ static void cedrus_h265_setup(struct cedrus_ctx *ctx,
}
/* Activate H265 engine. */
- cedrus_engine_enable(dev, CEDRUS_CODEC_H265);
+ cedrus_engine_enable(ctx, CEDRUS_CODEC_H265);
/* Source offset and length in bits. */
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
index 93347d3ba360..daf5f244f93b 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
@@ -30,7 +30,7 @@
#include "cedrus_hw.h"
#include "cedrus_regs.h"
-int cedrus_engine_enable(struct cedrus_dev *dev, enum cedrus_codec codec)
+int cedrus_engine_enable(struct cedrus_ctx *ctx, enum cedrus_codec codec)
{
u32 reg = 0;
@@ -58,7 +58,12 @@ int cedrus_engine_enable(struct cedrus_dev *dev, enum cedrus_codec codec)
return -EINVAL;
}
- cedrus_write(dev, VE_MODE, reg);
+ if (ctx->src_fmt.width == 4096)
+ reg |= VE_MODE_PIC_WIDTH_IS_4096;
+ if (ctx->src_fmt.width > 2048)
+ reg |= VE_MODE_PIC_WIDTH_MORE_2048;
+
+ cedrus_write(ctx->dev, VE_MODE, reg);
return 0;
}
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_hw.h b/drivers/staging/media/sunxi/cedrus/cedrus_hw.h
index 27d0882397aa..604ff932fbf5 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_hw.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_hw.h
@@ -16,7 +16,7 @@
#ifndef _CEDRUS_HW_H_
#define _CEDRUS_HW_H_
-int cedrus_engine_enable(struct cedrus_dev *dev, enum cedrus_codec codec);
+int cedrus_engine_enable(struct cedrus_ctx *ctx, enum cedrus_codec codec);
void cedrus_engine_disable(struct cedrus_dev *dev);
void cedrus_dst_format_set(struct cedrus_dev *dev,
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c b/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
index 13c34927bad5..8bcd6b8f9e2d 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_mpeg2.c
@@ -96,7 +96,7 @@ static void cedrus_mpeg2_setup(struct cedrus_ctx *ctx, struct cedrus_run *run)
quantization = run->mpeg2.quantization;
/* Activate MPEG engine. */
- cedrus_engine_enable(dev, CEDRUS_CODEC_MPEG2);
+ cedrus_engine_enable(ctx, CEDRUS_CODEC_MPEG2);
/* Set intra quantization matrix. */
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
index 4275a307d282..ace3d49fcd82 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
@@ -35,6 +35,8 @@
#define VE_MODE 0x00
+#define VE_MODE_PIC_WIDTH_IS_4096 BIT(22)
+#define VE_MODE_PIC_WIDTH_MORE_2048 BIT(21)
#define VE_MODE_REC_WR_MODE_2MB (0x01 << 20)
#define VE_MODE_REC_WR_MODE_1MB (0x00 << 20)
#define VE_MODE_DDR_MODE_BW_128 (0x03 << 16)
--
2.24.0

View file

@ -0,0 +1,211 @@
From 03e612e701a61aa9cc9fd8e25cd47d8d685ef675 Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@siol.net>
Date: Wed, 6 Nov 2019 22:05:37 +0100
Subject: [PATCH 2/3] media: cedrus: Fix H264 4k support
H264 decoder needs additional or bigger buffers in order to decode 4k
videos.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Acked-by: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
drivers/staging/media/sunxi/cedrus/cedrus.h | 7 ++
.../staging/media/sunxi/cedrus/cedrus_h264.c | 91 +++++++++++++++++--
.../staging/media/sunxi/cedrus/cedrus_regs.h | 11 +++
3 files changed, 101 insertions(+), 8 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.h b/drivers/staging/media/sunxi/cedrus/cedrus.h
index c45fb9a7ad07..96765555ab8a 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.h
@@ -116,8 +116,15 @@ struct cedrus_ctx {
ssize_t mv_col_buf_size;
void *pic_info_buf;
dma_addr_t pic_info_buf_dma;
+ ssize_t pic_info_buf_size;
void *neighbor_info_buf;
dma_addr_t neighbor_info_buf_dma;
+ void *deblk_buf;
+ dma_addr_t deblk_buf_dma;
+ ssize_t deblk_buf_size;
+ void *intra_pred_buf;
+ dma_addr_t intra_pred_buf_dma;
+ ssize_t intra_pred_buf_size;
} h264;
struct {
void *mv_col_buf;
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
index 8a09a08b1af2..bfb4a4820a67 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
@@ -39,7 +39,7 @@ struct cedrus_h264_sram_ref_pic {
#define CEDRUS_H264_FRAME_NUM 18
#define CEDRUS_NEIGHBOR_INFO_BUF_SIZE (16 * SZ_1K)
-#define CEDRUS_PIC_INFO_BUF_SIZE (128 * SZ_1K)
+#define CEDRUS_MIN_PIC_INFO_BUF_SIZE (130 * SZ_1K)
static void cedrus_h264_write_sram(struct cedrus_dev *dev,
enum cedrus_h264_sram_off off,
@@ -342,6 +342,20 @@ static void cedrus_set_params(struct cedrus_ctx *ctx,
VE_H264_VLD_ADDR_FIRST | VE_H264_VLD_ADDR_VALID |
VE_H264_VLD_ADDR_LAST);
+ if (ctx->src_fmt.width > 2048) {
+ cedrus_write(dev, VE_BUF_CTRL,
+ VE_BUF_CTRL_INTRAPRED_MIXED_RAM |
+ VE_BUF_CTRL_DBLK_MIXED_RAM);
+ cedrus_write(dev, VE_DBLK_DRAM_BUF_ADDR,
+ ctx->codec.h264.deblk_buf_dma);
+ cedrus_write(dev, VE_INTRAPRED_DRAM_BUF_ADDR,
+ ctx->codec.h264.intra_pred_buf_dma);
+ } else {
+ cedrus_write(dev, VE_BUF_CTRL,
+ VE_BUF_CTRL_INTRAPRED_INT_SRAM |
+ VE_BUF_CTRL_DBLK_INT_SRAM);
+ }
+
/*
* FIXME: Since the bitstream parsing is done in software, and
* in userspace, this shouldn't be needed anymore. But it
@@ -502,18 +516,30 @@ static void cedrus_h264_setup(struct cedrus_ctx *ctx,
static int cedrus_h264_start(struct cedrus_ctx *ctx)
{
struct cedrus_dev *dev = ctx->dev;
+ unsigned int pic_info_size;
unsigned int field_size;
unsigned int mv_col_size;
int ret;
+ /* Formula for picture buffer size is taken from CedarX source. */
+
+ if (ctx->src_fmt.width > 2048)
+ pic_info_size = CEDRUS_H264_FRAME_NUM * 0x4000;
+ else
+ pic_info_size = CEDRUS_H264_FRAME_NUM * 0x1000;
+
/*
- * FIXME: It seems that the H6 cedarX code is using a formula
- * here based on the size of the frame, while all the older
- * code is using a fixed size, so that might need to be
- * changed at some point.
+ * FIXME: If V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY is set,
+ * there is no need to multiply by 2.
*/
+ pic_info_size += ctx->src_fmt.height * 2 * 64;
+
+ if (pic_info_size < CEDRUS_MIN_PIC_INFO_BUF_SIZE)
+ pic_info_size = CEDRUS_MIN_PIC_INFO_BUF_SIZE;
+
+ ctx->codec.h264.pic_info_buf_size = pic_info_size;
ctx->codec.h264.pic_info_buf =
- dma_alloc_coherent(dev->dev, CEDRUS_PIC_INFO_BUF_SIZE,
+ dma_alloc_coherent(dev->dev, ctx->codec.h264.pic_info_buf_size,
&ctx->codec.h264.pic_info_buf_dma,
GFP_KERNEL);
if (!ctx->codec.h264.pic_info_buf)
@@ -566,15 +592,56 @@ static int cedrus_h264_start(struct cedrus_ctx *ctx)
goto err_neighbor_buf;
}
+ if (ctx->src_fmt.width > 2048) {
+ /*
+ * Formulas for deblock and intra prediction buffer sizes
+ * are taken from CedarX source.
+ */
+
+ ctx->codec.h264.deblk_buf_size =
+ ALIGN(ctx->src_fmt.width, 32) * 12;
+ ctx->codec.h264.deblk_buf =
+ dma_alloc_coherent(dev->dev,
+ ctx->codec.h264.deblk_buf_size,
+ &ctx->codec.h264.deblk_buf_dma,
+ GFP_KERNEL);
+ if (!ctx->codec.h264.deblk_buf) {
+ ret = -ENOMEM;
+ goto err_mv_col_buf;
+ }
+
+ ctx->codec.h264.intra_pred_buf_size =
+ ALIGN(ctx->src_fmt.width, 64) * 5;
+ ctx->codec.h264.intra_pred_buf =
+ dma_alloc_coherent(dev->dev,
+ ctx->codec.h264.intra_pred_buf_size,
+ &ctx->codec.h264.intra_pred_buf_dma,
+ GFP_KERNEL);
+ if (!ctx->codec.h264.intra_pred_buf) {
+ ret = -ENOMEM;
+ goto err_deblk_buf;
+ }
+ }
+
return 0;
+err_deblk_buf:
+ dma_free_coherent(dev->dev, ctx->codec.h264.deblk_buf_size,
+ ctx->codec.h264.deblk_buf,
+ ctx->codec.h264.deblk_buf_dma);
+
+err_mv_col_buf:
+ dma_free_coherent(dev->dev, ctx->codec.h264.mv_col_buf_size,
+ ctx->codec.h264.mv_col_buf,
+ ctx->codec.h264.mv_col_buf_dma);
+
err_neighbor_buf:
dma_free_coherent(dev->dev, CEDRUS_NEIGHBOR_INFO_BUF_SIZE,
ctx->codec.h264.neighbor_info_buf,
ctx->codec.h264.neighbor_info_buf_dma);
err_pic_buf:
- dma_free_coherent(dev->dev, CEDRUS_PIC_INFO_BUF_SIZE,
+ dma_free_coherent(dev->dev, ctx->codec.h264.pic_info_buf_size,
ctx->codec.h264.pic_info_buf,
ctx->codec.h264.pic_info_buf_dma);
return ret;
@@ -590,9 +657,17 @@ static void cedrus_h264_stop(struct cedrus_ctx *ctx)
dma_free_coherent(dev->dev, CEDRUS_NEIGHBOR_INFO_BUF_SIZE,
ctx->codec.h264.neighbor_info_buf,
ctx->codec.h264.neighbor_info_buf_dma);
- dma_free_coherent(dev->dev, CEDRUS_PIC_INFO_BUF_SIZE,
+ dma_free_coherent(dev->dev, ctx->codec.h264.pic_info_buf_size,
ctx->codec.h264.pic_info_buf,
ctx->codec.h264.pic_info_buf_dma);
+ if (ctx->codec.h264.deblk_buf_size)
+ dma_free_coherent(dev->dev, ctx->codec.h264.deblk_buf_size,
+ ctx->codec.h264.deblk_buf,
+ ctx->codec.h264.deblk_buf_dma);
+ if (ctx->codec.h264.intra_pred_buf_size)
+ dma_free_coherent(dev->dev, ctx->codec.h264.intra_pred_buf_size,
+ ctx->codec.h264.intra_pred_buf,
+ ctx->codec.h264.intra_pred_buf_dma);
}
static void cedrus_h264_trigger(struct cedrus_ctx *ctx)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
index ace3d49fcd82..7beb03d3bb39 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
@@ -46,6 +46,17 @@
#define VE_MODE_DEC_H264 (0x01 << 0)
#define VE_MODE_DEC_MPEG (0x00 << 0)
+#define VE_BUF_CTRL 0x50
+
+#define VE_BUF_CTRL_INTRAPRED_EXT_RAM (0x02 << 2)
+#define VE_BUF_CTRL_INTRAPRED_MIXED_RAM (0x01 << 2)
+#define VE_BUF_CTRL_INTRAPRED_INT_SRAM (0x00 << 2)
+#define VE_BUF_CTRL_DBLK_EXT_RAM (0x02 << 0)
+#define VE_BUF_CTRL_DBLK_MIXED_RAM (0x01 << 0)
+#define VE_BUF_CTRL_DBLK_INT_SRAM (0x00 << 0)
+
+#define VE_DBLK_DRAM_BUF_ADDR 0x54
+#define VE_INTRAPRED_DRAM_BUF_ADDR 0x58
#define VE_PRIMARY_CHROMA_BUF_LEN 0xc4
#define VE_PRIMARY_FB_LINE_STRIDE 0xc8
--
2.24.0

View file

@ -0,0 +1,36 @@
From 0b3e5c15f9cb8b56599c50e6bf4f46ee1c1253bc Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@siol.net>
Date: Wed, 6 Nov 2019 22:05:38 +0100
Subject: [PATCH 3/3] media: cedrus: Increase maximum supported size
There are few variations of 4k resolutions. The biggest one is
4096x2304 which is also supported by HW. It has also nice property that
both width and size are divisible by maximum HEVC CTB size, which is 64.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Acked-by: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
---
drivers/staging/media/sunxi/cedrus/cedrus_video.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
index cc15a5cf107d..15cf1f10221b 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
@@ -29,8 +29,8 @@
#define CEDRUS_MIN_WIDTH 16U
#define CEDRUS_MIN_HEIGHT 16U
-#define CEDRUS_MAX_WIDTH 3840U
-#define CEDRUS_MAX_HEIGHT 2160U
+#define CEDRUS_MAX_WIDTH 4096U
+#define CEDRUS_MAX_HEIGHT 2304U
static struct cedrus_format cedrus_formats[] = {
{
--
2.24.0

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,89 @@
From 60808cc1810d47f91c368de8ffb7db59cabceaf9 Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@siol.net>
Date: Tue, 28 May 2019 21:05:34 +0200
Subject: [PATCH] 10-bit HEVC hack
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
.../staging/media/sunxi/cedrus/cedrus_h265.c | 12 +++++++++++
.../staging/media/sunxi/cedrus/cedrus_regs.h | 4 ++++
.../staging/media/sunxi/cedrus/cedrus_video.c | 20 ++++++++++++++-----
3 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h265.c b/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
index 97dce6ffbbc5..d866662cd5a5 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h265.c
@@ -520,6 +520,18 @@ static void cedrus_h265_setup(struct cedrus_ctx *ctx,
cedrus_write(dev, VE_DEC_H265_DEC_PCM_CTRL, reg);
+ if (sps->bit_depth_luma_minus8) {
+ unsigned int size;
+
+ size = ALIGN(ctx->src_fmt.width, 16) * ALIGN(ctx->src_fmt.height, 16);
+
+ reg = (size * 3) / 2;
+ cedrus_write(dev, VE_DEC_H265_OFFSET_ADDR_FIRST_OUT, reg);
+
+ reg = DIV_ROUND_UP(ctx->src_fmt.width, 4);
+ cedrus_write(dev, VE_DEC_H265_10BIT_CONFIGURE, ALIGN(reg, 32));
+ }
+
/* PPS. */
reg = VE_DEC_H265_DEC_PPS_CTRL0_PPS_CR_QP_OFFSET(pps->pps_cr_qp_offset) |
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
index df1cceef8d93..150eae2d92d2 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
@@ -498,6 +498,10 @@
#define VE_DEC_H265_LOW_ADDR (VE_ENGINE_DEC_H265 + 0x80)
+#define VE_DEC_H265_OFFSET_ADDR_FIRST_OUT (VE_ENGINE_DEC_H265 + 0x84)
+#define VE_DEC_H265_OFFSET_ADDR_SECOND_OUT (VE_ENGINE_DEC_H265 + 0x88)
+#define VE_DEC_H265_10BIT_CONFIGURE (VE_ENGINE_DEC_H265 + 0x8c)
+
#define VE_DEC_H265_LOW_ADDR_PRIMARY_CHROMA(a) \
SHIFT_AND_MASK_BITS(a, 31, 24)
#define VE_DEC_H265_LOW_ADDR_SECONDARY_CHROMA(a) \
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
index 497b1199d3fe..178ad45b79d8 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
@@ -367,17 +367,27 @@ static int cedrus_queue_setup(struct vb2_queue *vq, unsigned int *nbufs,
{
struct cedrus_ctx *ctx = vb2_get_drv_priv(vq);
struct v4l2_pix_format *pix_fmt;
+ unsigned int extra_size = 0;
- if (V4L2_TYPE_IS_OUTPUT(vq->type))
+ if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
pix_fmt = &ctx->src_fmt;
- else
+ } else {
pix_fmt = &ctx->dst_fmt;
+ /* The HEVC decoder needs extra size on the output buffer. */
+ if (ctx->src_fmt.pixelformat == V4L2_PIX_FMT_HEVC_SLICE) {
+ extra_size = DIV_ROUND_UP(pix_fmt->width, 4);
+ extra_size = ALIGN(extra_size, 32);
+ extra_size *= ALIGN(pix_fmt->height, 16) * 3;
+ extra_size /= 2;
+ }
+ }
+
if (*nplanes) {
- if (sizes[0] < pix_fmt->sizeimage)
- return -EINVAL;
+ if (sizes[0] < (pix_fmt->sizeimage + extra_size))
+ sizes[0] = pix_fmt->sizeimage + extra_size;
} else {
- sizes[0] = pix_fmt->sizeimage;
+ sizes[0] = pix_fmt->sizeimage + extra_size;
*nplanes = 1;
}
--
2.23.0

View file

@ -31,7 +31,7 @@ index d0a8d5810c0a..9a715a6bdbf9 100644
#define SUN4I_I2S_FIFO_TX_REG 0x0c
#define SUN4I_I2S_FIFO_RX_REG 0x10
@@ -97,33 +100,53 @@
@@ -100,22 +100,25 @@
#define SUN8I_I2S_CTRL_MODE_PCM (0 << 4)
#define SUN8I_I2S_FMT0_LRCLK_POLARITY_MASK BIT(19)
@ -63,9 +63,8 @@ index d0a8d5810c0a..9a715a6bdbf9 100644
#define SUN8I_I2S_TX_CHAN_MAP_REG 0x44
#define SUN8I_I2S_TX_CHAN_SEL_REG 0x34
#define SUN8I_I2S_TX_CHAN_OFFSET_MASK GENMASK(13, 12)
-#define SUN8I_I2S_TX_CHAN_OFFSET(offset) (offset << 12)
+#define SUN8I_I2S_TX_CHAN_OFFSET(offset) ((offset) << 12)
@@ -123,10 +126,27 @@
#define SUN8I_I2S_TX_CHAN_OFFSET(offset) ((offset) << 12)
#define SUN8I_I2S_TX_CHAN_EN_MASK GENMASK(11, 4)
#define SUN8I_I2S_TX_CHAN_EN(num_chan) (((1 << num_chan) - 1) << 4)
+#define SUN8I_I2S_TX_CHAN_SEL_MASK GENMASK(2, 0)
@ -485,18 +484,19 @@ index d0a8d5810c0a..9a715a6bdbf9 100644
break;
default:
@@ -661,12 +928,8 @@ static int sun8i_i2s_set_soc_fmt(const struct sun4i_i2s *i2s,
@@ -913,12 +933,9 @@ static int sun8i_i2s_set_soc_fmt(struct sun4i_i2s *i2s,
regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG,
SUN8I_I2S_CTRL_MODE_MASK, mode);
- regmap_update_bits(i2s->regmap, SUN8I_I2S_TX_CHAN_SEL_REG,
- SUN8I_I2S_TX_CHAN_OFFSET_MASK,
- SUN8I_I2S_TX_CHAN_OFFSET(offset));
- SUN8I_I2S_TX_CHAN_OFFSET(i2s->pinephone_hack ? 0 : offset));
- regmap_update_bits(i2s->regmap, SUN8I_I2S_RX_CHAN_SEL_REG,
- SUN8I_I2S_TX_CHAN_OFFSET_MASK,
- SUN8I_I2S_TX_CHAN_OFFSET(offset));
+ i2s->variant->set_txchanoffset(i2s, 0);
+ i2s->variant->set_rxchanoffset(i2s);
+
/* DAI clock master masks */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {

View file

@ -0,0 +1,569 @@
diff -Naur linux-5.3-rc2-old/drivers/mfd/ac200.c linux-5.3-rc2-old-new/drivers/mfd/ac200.c
--- linux-5.3-rc2-old/drivers/mfd/ac200.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-5.3-rc2-old-new/drivers/mfd/ac200.c 2019-08-03 15:24:25.929999997 +0200
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MFD core driver for X-Powers' AC200 IC
+ *
+ * The AC200 is a chip which is co-packaged with Allwinner H6 SoC and
+ * includes analog audio codec, analog TV encoder, ethernet PHY, eFuse
+ * and RTC.
+ *
+ * Copyright (c) 2019 Jernej Skrabec <jernej.skrabec@siol.net>
+ *
+ * Based on AC100 driver with following copyrights:
+ * Copyright (2016) Chen-Yu Tsai
+ */
+
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/ac200.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+struct ac200_dev {
+ struct clk *clk;
+ /*
+ * Lock is needed for serializing concurrent access to
+ * AC200 registers in order not to mess with register page.
+ */
+ struct mutex lock;
+ struct regmap *regmap;
+};
+
+/*
+ * Register values can't be cached because registers are divided
+ * into multiple pages.
+ */
+static const struct regmap_config ac200_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+};
+
+int ac200_reg_read(struct ac200_dev *ac200, u16 reg, u16 *value)
+{
+ unsigned int val;
+ int ret;
+
+ mutex_lock(&ac200->lock);
+ ret = regmap_write(ac200->regmap, AC200_TWI_REG_ADDR_H, reg >> 8);
+ if (ret)
+ goto read_reg_out;
+
+ ret = regmap_read(ac200->regmap, reg & 0xff, &val);
+ *value = val;
+
+read_reg_out:
+ mutex_unlock(&ac200->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ac200_reg_read);
+
+int ac200_reg_write(struct ac200_dev *ac200, u16 reg, u16 value)
+{
+ int ret;
+
+ mutex_lock(&ac200->lock);
+ ret = regmap_write(ac200->regmap, AC200_TWI_REG_ADDR_H, reg >> 8);
+ if (ret)
+ goto write_reg_out;
+
+ ret = regmap_write(ac200->regmap, reg & 0xff, value);
+
+write_reg_out:
+ mutex_unlock(&ac200->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ac200_reg_write);
+
+int ac200_reg_mod(struct ac200_dev *ac200, u16 reg, u16 mask, u16 value)
+{
+ unsigned int val;
+ int ret;
+
+ mutex_lock(&ac200->lock);
+ ret = regmap_write(ac200->regmap, AC200_TWI_REG_ADDR_H, reg >> 8);
+ if (ret)
+ goto mod_reg_out;
+
+ ret = regmap_read(ac200->regmap, reg & 0xff, &val);
+ if (ret)
+ goto mod_reg_out;
+
+ val &= ~mask;
+ val |= value;
+
+ ret = regmap_write(ac200->regmap, reg & 0xff, val);
+
+mod_reg_out:
+ mutex_unlock(&ac200->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ac200_reg_mod);
+
+static struct mfd_cell ac200_cells[] = {
+ {
+ .name = "ac200-codec",
+ .of_compatible = "x-powers,ac200-codec",
+ }, {
+ .name = "ac200-efuse",
+ .of_compatible = "x-powers,ac200-efuse",
+ }, {
+ .name = "ac200-ephy",
+ .of_compatible = "x-powers,ac200-ephy",
+ }, {
+ .name = "ac200-rtc",
+ .of_compatible = "x-powers,ac200-rtc",
+ }, {
+ .name = "ac200-tve",
+ .of_compatible = "x-powers,ac200-tve",
+ },
+};
+
+static int ac200_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &i2c->dev;
+ struct ac200_dev *ac200;
+ int ret;
+
+ ac200 = devm_kzalloc(dev, sizeof(*ac200), GFP_KERNEL);
+ if (!ac200)
+ return -ENOMEM;
+
+ mutex_init(&ac200->lock);
+ i2c_set_clientdata(i2c, ac200);
+
+ ac200->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(ac200->clk)) {
+ ret = PTR_ERR(ac200->clk);
+ dev_err(dev, "Can't obtain the clock: %d\n", ret);
+ return ret;
+ }
+
+ ac200->regmap = devm_regmap_init_i2c(i2c, &ac200_regmap_config);
+ if (IS_ERR(ac200->regmap)) {
+ ret = PTR_ERR(ac200->regmap);
+ dev_err(dev, "Regmap init failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(ac200->clk);
+ if (ret) {
+ dev_err(dev, "Can't enable the clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = ac200_reg_write(ac200, AC200_SYS_CONTROL, 0);
+ if (ret) {
+ dev_err(dev, "Can't put AC200 in reset: %d\n", ret);
+ goto err;
+ }
+
+ ret = ac200_reg_write(ac200, AC200_SYS_CONTROL, 1);
+ if (ret) {
+ dev_err(dev, "Can't put AC200 out of reset: %d\n", ret);
+ goto err;
+ }
+
+ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ac200_cells,
+ ARRAY_SIZE(ac200_cells), NULL, 0, NULL);
+ if (ret) {
+ dev_err(dev, "Failed to add MFD devices: %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ clk_disable_unprepare(ac200->clk);
+ return ret;
+}
+
+static int ac200_i2c_remove(struct i2c_client *i2c)
+{
+ struct ac200_dev *ac200 = i2c_get_clientdata(i2c);
+
+ ac200_reg_write(ac200, AC200_SYS_CONTROL, 0);
+
+ clk_disable_unprepare(ac200->clk);
+
+ return 0;
+}
+
+static const struct i2c_device_id ac200_ids[] = {
+ { "ac200", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, ac200_ids);
+
+static const struct of_device_id ac200_of_match[] = {
+ { .compatible = "x-powers,ac200" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ac200_of_match);
+
+static struct i2c_driver ac200_i2c_driver = {
+ .driver = {
+ .name = "ac200",
+ .of_match_table = of_match_ptr(ac200_of_match),
+ },
+ .probe = ac200_i2c_probe,
+ .remove = ac200_i2c_remove,
+ .id_table = ac200_ids,
+};
+module_i2c_driver(ac200_i2c_driver);
+
+MODULE_DESCRIPTION("MFD core driver for AC200");
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
+MODULE_LICENSE("GPL v2");
diff -Naur linux-5.3-rc2-old/drivers/mfd/Kconfig linux-5.3-rc2-old-new/drivers/mfd/Kconfig
--- linux-5.3-rc2-old/drivers/mfd/Kconfig 2019-07-28 21:47:02.000000000 +0200
+++ linux-5.3-rc2-old-new/drivers/mfd/Kconfig 2019-08-03 15:24:25.929999997 +0200
@@ -178,6 +178,15 @@
This driver include only the core APIs. You have to select individual
components like codecs or RTC under the corresponding menus.
+config MFD_AC200
+ tristate "X-Powers AC200"
+ select MFD_CORE
+ depends on I2C
+ help
+ If you say Y here you get support for the X-Powers AC200 IC.
+ This driver include only the core APIs. You have to select individual
+ components like Ethernet PHY or RTC under the corresponding menus.
+
config MFD_AXP20X
tristate
select MFD_CORE
diff -Naur linux-5.3-rc2-old/drivers/mfd/Makefile linux-5.3-rc2-old-new/drivers/mfd/Makefile
--- linux-5.3-rc2-old/drivers/mfd/Makefile 2019-07-28 21:47:02.000000000 +0200
+++ linux-5.3-rc2-old-new/drivers/mfd/Makefile 2019-08-03 15:24:25.929999997 +0200
@@ -143,6 +143,7 @@
obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o
obj-$(CONFIG_MFD_AC100) += ac100.o
+obj-$(CONFIG_MFD_AC200) += ac200.o
obj-$(CONFIG_MFD_AXP20X) += axp20x.o
obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o
obj-$(CONFIG_MFD_AXP20X_RSB) += axp20x-rsb.o
diff -Naur linux-5.3-rc2-old/drivers/net/phy/ac200.c linux-5.3-rc2-old-new/drivers/net/phy/ac200.c
--- linux-5.3-rc2-old/drivers/net/phy/ac200.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-5.3-rc2-old-new/drivers/net/phy/ac200.c 2019-08-03 15:24:25.929999997 +0200
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0+
+/**
+ * Driver for AC200 Ethernet PHY
+ *
+ * Copyright (c) 2019 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/ac200.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+
+#define AC200_EPHY_ID 0x00441400
+#define AC200_EPHY_ID_MASK 0x0ffffff0
+
+/* macros for system ephy control 0 register */
+#define AC200_EPHY_RESET_INVALID BIT(0)
+#define AC200_EPHY_SYSCLK_GATING BIT(1)
+
+/* macros for system ephy control 1 register */
+#define AC200_EPHY_E_EPHY_MII_IO_EN BIT(0)
+#define AC200_EPHY_E_LNK_LED_IO_EN BIT(1)
+#define AC200_EPHY_E_SPD_LED_IO_EN BIT(2)
+#define AC200_EPHY_E_DPX_LED_IO_EN BIT(3)
+
+/* macros for ephy control register */
+#define AC200_EPHY_SHUTDOWN BIT(0)
+#define AC200_EPHY_LED_POL BIT(1)
+#define AC200_EPHY_CLK_SEL BIT(2)
+#define AC200_EPHY_ADDR(x) (((x) & 0x1F) << 4)
+#define AC200_EPHY_XMII_SEL BIT(11)
+#define AC200_EPHY_CALIB(x) (((x) & 0xF) << 12)
+
+struct ac200_ephy_dev {
+ struct phy_driver *ephy;
+ struct ac200_dev *ac200;
+};
+
+static char *ac200_phy_name = "AC200 EPHY";
+
+static void disable_intelligent_ieee(struct phy_device *phydev)
+{
+ unsigned int value;
+
+ phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */
+ value = phy_read(phydev, 0x17);
+ value &= ~BIT(3); /* disable IEEE */
+ phy_write(phydev, 0x17, value);
+ phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */
+}
+
+static void disable_802_3az_ieee(struct phy_device *phydev)
+{
+ unsigned int value;
+
+ phy_write(phydev, 0xd, 0x7);
+ phy_write(phydev, 0xe, 0x3c);
+ phy_write(phydev, 0xd, BIT(14) | 0x7);
+ value = phy_read(phydev, 0xe);
+ value &= ~BIT(1);
+ phy_write(phydev, 0xd, 0x7);
+ phy_write(phydev, 0xe, 0x3c);
+ phy_write(phydev, 0xd, BIT(14) | 0x7);
+ phy_write(phydev, 0xe, value);
+
+ phy_write(phydev, 0x1f, 0x0200); /* switch to page 2 */
+ phy_write(phydev, 0x18, 0x0000);
+}
+
+static int ac200_ephy_config_init(struct phy_device *phydev)
+{
+ const struct ac200_ephy_dev *priv = phydev->drv->driver_data;
+ u16 value;
+
+ phy_write(phydev, 0x1f, 0x0100); /* Switch to Page 1 */
+ phy_write(phydev, 0x12, 0x4824); /* Disable APS */
+
+ phy_write(phydev, 0x1f, 0x0200); /* Switch to Page 2 */
+ phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */
+
+ phy_write(phydev, 0x1f, 0x0600); /* Switch to Page 6 */
+ phy_write(phydev, 0x14, 0x708f); /* PHYAFE TX optimization */
+ phy_write(phydev, 0x13, 0xF000); /* PHYAFE RX optimization */
+ phy_write(phydev, 0x15, 0x1530);
+
+ phy_write(phydev, 0x1f, 0x0800); /* Switch to Page 6 */
+ phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */
+
+ disable_intelligent_ieee(phydev); /* Disable Intelligent IEEE */
+ disable_802_3az_ieee(phydev); /* Disable 802.3az IEEE */
+ phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */
+
+ value = (phydev->interface == PHY_INTERFACE_MODE_RMII) ?
+ AC200_EPHY_XMII_SEL : 0;
+ ac200_reg_mod(priv->ac200, AC200_EPHY_CTL, AC200_EPHY_XMII_SEL, value);
+
+ /* FIXME: This is probably H6 specific */
+ value = phy_read(phydev, 0x13);
+ value |= BIT(12);
+ phy_write(phydev, 0x13, value);
+
+ return 0;
+
+}
+
+static const struct mdio_device_id __maybe_unused ac200_ephy_phy_tbl[] = {
+ { AC200_EPHY_ID, AC200_EPHY_ID_MASK },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(mdio, ac200_ephy_phy_tbl);
+
+static int ac200_ephy_probe(struct platform_device *pdev)
+{
+ struct ac200_dev *ac200 = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct ac200_ephy_dev *priv;
+ struct nvmem_cell *calcell;
+ struct phy_driver *ephy;
+ u16 *caldata, calib;
+ size_t callen;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ ephy = devm_kzalloc(dev, sizeof(*ephy), GFP_KERNEL);
+ if (!ephy)
+ return -ENOMEM;
+
+ calcell = devm_nvmem_cell_get(dev, "ephy_calib");
+ if (IS_ERR(calcell)) {
+ dev_err(dev, "Unable to find calibration data!\n");
+ return PTR_ERR(calcell);
+ }
+
+ caldata = nvmem_cell_read(calcell, &callen);
+ if (IS_ERR(caldata)) {
+ dev_err(dev, "Unable to read calibration data!\n");
+ return PTR_ERR(caldata);
+ }
+
+ if (callen != 2) {
+ dev_err(dev, "Calibration data has wrong length: 2 != %lu\n",
+ callen);
+ return -EINVAL;
+ }
+
+ calib = *caldata + 3;
+ kfree(caldata);
+
+ ephy->phy_id = AC200_EPHY_ID;
+ ephy->phy_id_mask = AC200_EPHY_ID_MASK;
+ ephy->name = ac200_phy_name;
+ ephy->driver_data = priv;
+ ephy->soft_reset = genphy_soft_reset;
+ ephy->config_init = ac200_ephy_config_init;
+ ephy->suspend = genphy_suspend;
+ ephy->resume = genphy_resume;
+
+ priv->ac200 = ac200;
+ priv->ephy = ephy;
+ platform_set_drvdata(pdev, priv);
+
+ ret = ac200_reg_write(ac200, AC200_SYS_EPHY_CTL0,
+ AC200_EPHY_RESET_INVALID |
+ AC200_EPHY_SYSCLK_GATING);
+ if (ret)
+ return ret;
+
+ ret = ac200_reg_write(ac200, AC200_SYS_EPHY_CTL1,
+ AC200_EPHY_E_EPHY_MII_IO_EN |
+ AC200_EPHY_E_LNK_LED_IO_EN |
+ AC200_EPHY_E_SPD_LED_IO_EN |
+ AC200_EPHY_E_DPX_LED_IO_EN);
+ if (ret)
+ return ret;
+
+ ret = ac200_reg_write(ac200, AC200_EPHY_CTL,
+ AC200_EPHY_LED_POL |
+ AC200_EPHY_CLK_SEL |
+ AC200_EPHY_ADDR(1) |
+ AC200_EPHY_CALIB(calib));
+ if (ret)
+ return ret;
+
+ ret = phy_driver_register(priv->ephy, THIS_MODULE);
+ if (ret) {
+ dev_err(dev, "Unable to register phy\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ac200_ephy_remove(struct platform_device *pdev)
+{
+ struct ac200_ephy_dev *priv = platform_get_drvdata(pdev);
+
+ phy_driver_unregister(priv->ephy);
+
+ ac200_reg_write(priv->ac200, AC200_EPHY_CTL,
+ AC200_EPHY_SHUTDOWN);
+ ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL1, 0);
+ ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL0, 0);
+
+ return 0;
+}
+
+static const struct of_device_id ac200_ephy_match[] = {
+ { .compatible = "x-powers,ac200-ephy" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ac200_ephy_match);
+
+static struct platform_driver ac200_ephy_driver = {
+ .probe = ac200_ephy_probe,
+ .remove = ac200_ephy_remove,
+ .driver = {
+ .name = "ac200-ephy",
+ .of_match_table = ac200_ephy_match,
+ },
+};
+module_platform_driver(ac200_ephy_driver);
+
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>>");
+MODULE_DESCRIPTION("AC200 Ethernet PHY driver");
+MODULE_LICENSE("GPL");
diff -Naur linux-5.3-rc2-old/drivers/net/phy/Kconfig linux-5.3-rc2-old-new/drivers/net/phy/Kconfig
--- linux-5.3-rc2-old/drivers/net/phy/Kconfig 2019-07-28 21:47:02.000000000 +0200
+++ linux-5.3-rc2-old-new/drivers/net/phy/Kconfig 2019-08-03 15:24:25.929999997 +0200
@@ -244,6 +244,13 @@
depends on HWMON || HWMON=n
select MDIO_I2C
+config AC200_PHY
+ tristate "AC200 EPHY"
+ depends on NVMEM
+ depends on OF
+ help
+ Fast ethernet PHY as found in X-Powers AC200 multi-function device.
+
config AMD_PHY
tristate "AMD PHYs"
---help---
diff -Naur linux-5.3-rc2-old/drivers/net/phy/Makefile linux-5.3-rc2-old-new/drivers/net/phy/Makefile
--- linux-5.3-rc2-old/drivers/net/phy/Makefile 2019-07-28 21:47:02.000000000 +0200
+++ linux-5.3-rc2-old-new/drivers/net/phy/Makefile 2019-08-03 15:24:25.929999997 +0200
@@ -46,6 +46,7 @@
sfp-obj-$(CONFIG_SFP) += sfp-bus.o
obj-y += $(sfp-obj-y) $(sfp-obj-m)
+obj-$(CONFIG_AC200_PHY) += ac200.o
obj-$(CONFIG_ADIN_PHY) += adin.o
obj-$(CONFIG_AMD_PHY) += amd.o
aquantia-objs += aquantia_main.o
diff -Naur linux-5.3-rc2-old/include/linux/mfd/ac200.h linux-5.3-rc2-old-new/include/linux/mfd/ac200.h
--- linux-5.3-rc2-old/include/linux/mfd/ac200.h 1970-01-01 01:00:00.000000000 +0100
+++ linux-5.3-rc2-old-new/include/linux/mfd/ac200.h 2019-08-03 15:24:25.929999997 +0200
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Functions and registers to access AC200 IC.
+ *
+ * Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#ifndef __LINUX_MFD_AC200_H
+#define __LINUX_MFD_AC200_H
+
+#include <linux/types.h>
+
+struct ac200_dev;
+
+/* interface registers (can be accessed from any page) */
+#define AC200_TWI_CHANGE_TO_RSB 0x3E
+#define AC200_TWI_PAD_DELAY 0xC4
+#define AC200_TWI_REG_ADDR_H 0xFE
+
+/* General registers */
+#define AC200_SYS_VERSION 0x0000
+#define AC200_SYS_CONTROL 0x0002
+#define AC200_SYS_IRQ_ENABLE 0x0004
+#define AC200_SYS_IRQ_STATUS 0x0006
+#define AC200_SYS_CLK_CTL 0x0008
+#define AC200_SYS_DLDO_OSC_CTL 0x000A
+#define AC200_SYS_PLL_CTL0 0x000C
+#define AC200_SYS_PLL_CTL1 0x000E
+#define AC200_SYS_AUDIO_CTL0 0x0010
+#define AC200_SYS_AUDIO_CTL1 0x0012
+#define AC200_SYS_EPHY_CTL0 0x0014
+#define AC200_SYS_EPHY_CTL1 0x0016
+#define AC200_SYS_TVE_CTL0 0x0018
+#define AC200_SYS_TVE_CTL1 0x001A
+
+/* EPHY registers */
+#define AC200_EPHY_CTL 0x6000
+#define AC200_EPHY_BIST 0x6002
+
+int ac200_reg_read(struct ac200_dev *ac200, u16 reg, u16 *value);
+int ac200_reg_write(struct ac200_dev *ac200, u16 reg, u16 value);
+int ac200_reg_mod(struct ac200_dev *ac200, u16 reg, u16 mask, u16 value);
+
+#endif /* __LINUX_MFD_AC200_H */

View file

@ -0,0 +1,79 @@
diff -Naur linux-5.3-rc6-old/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi linux-5.3-rc6-new/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
--- linux-5.3-rc6-old/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi 2019-09-02 12:17:46.116666674 +0200
+++ linux-5.3-rc6-new/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi 2019-09-02 12:31:05.253333344 +0200
@@ -17,6 +17,16 @@
#address-cells = <1>;
#size-cells = <1>;
+ ac200_pwm_clk: ac200_clk {
+ compatible = "pwm-clock";
+ #clock-cells = <0>;
+ clock-frequency = <24000000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pwm1_pin>;
+ pwms = <&pwm 1 42 0>;
+ status = "disabled";
+ };
+
cpus {
#address-cells = <1>;
#size-cells = <0>;
@@ -241,7 +250,10 @@
reg = <0x03006000 0x400>;
#address-cells = <1>;
#size-cells = <1>;
+ ephy_calib: ephy_calib@2c {
+ reg = <0x2c 0x2>;
+ };
speedbin_efuse: speed@1c {
reg = <0x1c 0x4>;
};
@@ -326,6 +341,16 @@
pins = "PH0", "PH1";
function = "uart0";
};
+
+ i2c3_pins: i2c3-pins {
+ pins = "PB17", "PB18";
+ function = "i2c3";
+ };
+
+ pwm1_pin: pwm1-pin {
+ pins = "PB19";
+ function = "pwm1";
+ };
};
gic: interrupt-controller@3021000 {
@@ -431,6 +456,30 @@
status = "disabled";
};
+ i2c3: i2c@5002c00 {
+ compatible = "allwinner,sun6i-a31-i2c";
+ reg = <0x05002c00 0x400>;
+ interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_I2C3>;
+ resets = <&ccu RST_BUS_I2C3>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c3_pins>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ac200: mfd@10 {
+ compatible = "x-powers,ac200";
+ reg = <0x10>;
+ clocks = <&ac200_pwm_clk>;
+
+ ac200_ephy: phy {
+ compatible = "x-powers,ac200-ephy";
+ nvmem-cells = <&ephy_calib>;
+ nvmem-cell-names = "ephy_calib";
+ };
+ };
+ };
+
emac: ethernet@5020000 {
compatible = "allwinner,sun50i-h6-emac",
"allwinner,sun50i-a64-emac";

View file

@ -0,0 +1,363 @@
H6 PWM block is basically the same as A20 PWM, except that it also has
bus clock and reset line which needs to be handled accordingly.
Expand Allwinner PWM binding with H6 PWM specifics.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
.../bindings/pwm/allwinner,sun4i-a10-pwm.yaml | 36 ++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
index 0ac52f83a58c..deca5d81802f 100644
--- a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
+++ b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
@@ -30,13 +30,47 @@ properties:
- items:
- const: allwinner,sun50i-h5-pwm
- const: allwinner,sun5i-a13-pwm
+ - const: allwinner,sun50i-h6-pwm
reg:
maxItems: 1
- clocks:
+ # Even though it only applies to subschemas under the conditionals,
+ # not listing them here will trigger a warning because of the
+ # additionalsProperties set to false.
+ clocks: true
+ clock-names: true
+ resets:
maxItems: 1
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: allwinner,sun50i-h6-pwm
+
+ then:
+ properties:
+ clocks:
+ items:
+ - description: Module Clock
+ - description: Bus Clock
+
+ clock-names:
+ items:
+ - const: pwm
+ - const: bus
+
+ required:
+ - clock-names
+ - resets
+
+ else:
+ properties:
+ clocks:
+ maxItems: 1
+
required:
- "#pwm-cells"
- compatible
H6 PWM core needs deasserted reset line in order to work.
Add a quirk for it.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Acked-by: Maxime Ripard <maxime.ripard@bootlin.com>
---
drivers/pwm/pwm-sun4i.c | 27 +++++++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index de78c824bbfd..1b7be8fbde86 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -16,6 +16,7 @@
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
+#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>
@@ -72,12 +73,14 @@ static const u32 prescaler_table[] = {
struct sun4i_pwm_data {
bool has_prescaler_bypass;
+ bool has_reset;
unsigned int npwm;
};
struct sun4i_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
+ struct reset_control *rst;
void __iomem *base;
spinlock_t ctrl_lock;
const struct sun4i_pwm_data *data;
@@ -371,6 +374,14 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
if (IS_ERR(pwm->clk))
return PTR_ERR(pwm->clk);
+ if (pwm->data->has_reset) {
+ pwm->rst = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(pwm->rst))
+ return PTR_ERR(pwm->rst);
+
+ reset_control_deassert(pwm->rst);
+ }
+
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &sun4i_pwm_ops;
pwm->chip.base = -1;
@@ -383,19 +394,31 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
ret = pwmchip_add(&pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
- return ret;
+ goto err_pwm_add;
}
platform_set_drvdata(pdev, pwm);
return 0;
+
+err_pwm_add:
+ reset_control_assert(pwm->rst);
+
+ return ret;
}
static int sun4i_pwm_remove(struct platform_device *pdev)
{
struct sun4i_pwm_chip *pwm = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pwmchip_remove(&pwm->chip);
+ if (ret)
+ return ret;
- return pwmchip_remove(&pwm->chip);
+ reset_control_assert(pwm->rst);
+
+ return 0;
}
static struct platform_driver sun4i_pwm_driver = {
H6 PWM core needs bus clock to be enabled in order to work.
Add a quirk for it.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
drivers/pwm/pwm-sun4i.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 1b7be8fbde86..7d3ac3f2dc3f 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -72,6 +72,7 @@ static const u32 prescaler_table[] = {
};
struct sun4i_pwm_data {
+ bool has_bus_clock;
bool has_prescaler_bypass;
bool has_reset;
unsigned int npwm;
@@ -79,6 +80,7 @@ struct sun4i_pwm_data {
struct sun4i_pwm_chip {
struct pwm_chip chip;
+ struct clk *bus_clk;
struct clk *clk;
struct reset_control *rst;
void __iomem *base;
@@ -382,6 +384,16 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
reset_control_deassert(pwm->rst);
}
+ if (pwm->data->has_bus_clock) {
+ pwm->bus_clk = devm_clk_get(&pdev->dev, "bus");
+ if (IS_ERR(pwm->bus_clk)) {
+ ret = PTR_ERR(pwm->bus_clk);
+ goto err_bus;
+ }
+
+ clk_prepare_enable(pwm->bus_clk);
+ }
+
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &sun4i_pwm_ops;
pwm->chip.base = -1;
@@ -402,6 +414,8 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
return 0;
err_pwm_add:
+ clk_disable_unprepare(pwm->bus_clk);
+err_bus:
reset_control_assert(pwm->rst);
return ret;
@@ -416,6 +430,7 @@ static int sun4i_pwm_remove(struct platform_device *pdev)
if (ret)
return ret;
+ clk_disable_unprepare(pwm->bus_clk);
reset_control_assert(pwm->rst);
return 0;
Note that while H6 PWM has two channels, only first one is wired to
output pin. Second channel is used as a clock source to companion AC200
chip which is bundled into same package.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
drivers/pwm/pwm-sun4i.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 7d3ac3f2dc3f..9e0eca79ff88 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -331,6 +331,13 @@ static const struct sun4i_pwm_data sun4i_pwm_single_bypass = {
.npwm = 1,
};
+static const struct sun4i_pwm_data sun50i_pwm_dual_bypass_clk_rst = {
+ .has_bus_clock = true,
+ .has_prescaler_bypass = true,
+ .has_reset = true,
+ .npwm = 2,
+};
+
static const struct of_device_id sun4i_pwm_dt_ids[] = {
{
.compatible = "allwinner,sun4i-a10-pwm",
@@ -347,6 +354,9 @@ static const struct of_device_id sun4i_pwm_dt_ids[] = {
}, {
.compatible = "allwinner,sun8i-h3-pwm",
.data = &sun4i_pwm_single_bypass,
+ }, {
+ .compatible = "allwinner,sun50i-h6-pwm",
+ .data = &sun50i_pwm_dual_bypass_clk_rst,
}, {
/* sentinel */
},
This mode of operation is needed to achieve high enough frequency to
serve as clock source for AC200 chip, which is integrated into same
package as H6 SoC.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
drivers/pwm/pwm-sun4i.c | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 9e0eca79ff88..848cff26f385 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -120,6 +120,19 @@ static void sun4i_pwm_get_state(struct pwm_chip *chip,
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
+ /*
+ * PWM chapter in H6 manual has a diagram which explains that if bypass
+ * bit is set, no other setting has any meaning. Even more, experiment
+ * proved that also enable bit is ignored in this case.
+ */
+ if (val & BIT_CH(PWM_BYPASS, pwm->hwpwm)) {
+ state->period = DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC, clk_rate);
+ state->duty_cycle = state->period / 2;
+ state->polarity = PWM_POLARITY_NORMAL;
+ state->enabled = true;
+ return;
+ }
+
if ((PWM_REG_PRESCAL(val, pwm->hwpwm) == PWM_PRESCAL_MASK) &&
sun4i_pwm->data->has_prescaler_bypass)
prescaler = 1;
@@ -211,7 +224,8 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
{
struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip);
struct pwm_state cstate;
- u32 ctrl;
+ u32 ctrl, clk_rate;
+ bool bypass;
int ret;
unsigned int delay_us;
unsigned long now;
@@ -226,6 +240,16 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
}
}
+ /*
+ * Although it would make much more sense to check for bypass in
+ * sun4i_pwm_calculate(), value of bypass bit also depends on "enabled".
+ * Period is allowed to be rounded up or down.
+ */
+ clk_rate = clk_get_rate(sun4i_pwm->clk);
+ bypass = (state->period == NSEC_PER_SEC / clk_rate ||
+ state->period == DIV_ROUND_UP(NSEC_PER_SEC, clk_rate)) &&
+ state->enabled;
+
spin_lock(&sun4i_pwm->ctrl_lock);
ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
@@ -273,6 +297,11 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
}
+ if (bypass)
+ ctrl |= BIT_CH(PWM_BYPASS, pwm->hwpwm);
+ else
+ ctrl &= ~BIT_CH(PWM_BYPASS, pwm->hwpwm);
+
sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
spin_unlock(&sun4i_pwm->ctrl_lock);
Allwinner H6 PWM is similar to that in A20 except that it has additional
bus clock and reset line.
Note that first PWM channel is connected to output pin and second
channel is used internally, as a clock source to AC200 co-packaged chip.
This means that any combination of these two channels can be used and
thus it doesn't make sense to add pinctrl nodes at this point.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
index e8bed58e7246..c1abd805cfdc 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
@@ -229,6 +229,16 @@
status = "disabled";
};
+ pwm: pwm@300a000 {
+ compatible = "allwinner,sun50i-h6-pwm";
+ reg = <0x0300a000 0x400>;
+ clocks = <&osc24M>, <&ccu CLK_BUS_PWM>;
+ clock-names = "pwm", "bus";
+ resets = <&ccu RST_BUS_PWM>;
+ #pwm-cells = <3>;
+ status = "disabled";
+ };
+
pio: pinctrl@300b000 {
compatible = "allwinner,sun50i-h6-pinctrl";
reg = <0x0300b000 0x400>;