* [PATCH v1 0/8] Add support for DCMI camera interface of STMicroelectronics STM32 SoC series
From: Hugues Fruchet @ 2017-03-29 13:25 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Maxime Coquelin, Alexandre Torgue,
Mauro Carvalho Chehab, Hans Verkuil
Cc: devicetree, linux-kernel, Yannick Fertre, Benjamin Gaignard,
Hugues Fruchet, linux-arm-kernel, linux-media
This patchset introduces a basic support for Digital Camera Memory Interface
(DCMI) of STMicroelectronics STM32 SoC series.
This first basic support implements RGB565 & YUV frame grabbing.
Cropping and JPEG support will be added later on.
This has been tested on STM324x9I-EVAL evaluation board embedding
an OV2640 camera sensor.
This driver depends on:
- [PATCHv6 00/14] atmel-isi/ov7670/ov2640: convert to standalone drivers http://www.spinics.net/lists/linux-media/msg113480.html
===========
= history =
===========
version 1:
- Initial submission
===================
= v4l2-compliance =
===================
Below is the v4l2-compliance report for this current version of the DCMI camera interface.
v4l2-compliance has been built from v4l-utils-1.12.3.
v4l2-compliance SHA : f5f45e17ee98a0ebad7836ade2b34ceec909d751
Driver Info:
Driver name : stm32-dcmi
Card type : STM32 Digital Camera Memory Int
Bus info : platform:dcmi
Driver version: 4.11.0
Capabilities : 0x85200001
Video Capture
Read/Write
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x05200001
Video Capture
Read/Write
Streaming
Extended Pix Format
Compliance test for device /dev/video0 (not using libv4l2):
Required ioctls:
test VIDIOC_QUERYCAP: OK
Allow for multiple opens:
test second video open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 1 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Test input 0:
Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 3 Private Controls: 0
Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK
Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test VIDIOC_EXPBUF: OK
Test input 0:
Streaming ioctls:
test read/write: OK
test MMAP: OK
test USERPTR: OK (Not Supported)
test DMABUF: Cannot test, specify --expbuf-device
Total: 46, Succeeded: 46, Failed: 0, Warnings: 0
Hugues Fruchet (8):
dt-bindings: Document STM32 DCMI bindings
[media] stm32-dcmi: STM32 DCMI camera interface driver
ARM: dts: stm32: Enable DCMI support on STM32F429 MCU
ARM: dts: stm32: Enable DCMI camera interface on STM32F429-EVAL board
ARM: dts: stm32: Enable stmpe1600 gpio expandor of STM32F429-EVAL
board
ARM: dts: stm32: Enable ov2640 camera support of STM32F429-EVAL board
ARM: configs: stm32: stmpe 1600 GPIO expandor
ARM: configs: stm32: DCMI + OV2640 camera support
.../devicetree/bindings/media/st,stm32-dcmi.txt | 77 ++
arch/arm/boot/dts/stm32429i-eval.dts | 56 +
arch/arm/boot/dts/stm32f429.dtsi | 37 +
arch/arm/configs/stm32_defconfig | 9 +
drivers/media/platform/Kconfig | 12 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/stm32/Makefile | 1 +
drivers/media/platform/stm32/stm32-dcmi.c | 1417 ++++++++++++++++++++
8 files changed, 1611 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/st,stm32-dcmi.txt
create mode 100644 drivers/media/platform/stm32/Makefile
create mode 100644 drivers/media/platform/stm32/stm32-dcmi.c
--
1.9.1
^ permalink raw reply
* [PATCH v1 1/8] dt-bindings: Document STM32 DCMI bindings
From: Hugues Fruchet @ 2017-03-29 13:25 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Maxime Coquelin, Alexandre Torgue,
Mauro Carvalho Chehab, Hans Verkuil
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-media,
Benjamin Gaignard, Yannick Fertre, Hugues Fruchet
In-Reply-To: <1490793926-6477-1-git-send-email-hugues.fruchet@st.com>
This adds documentation of device tree bindings for the STM32 DCMI
(Digital Camera Memory Interface).
Signed-off-by: Hugues Fruchet <hugues.fruchet@st.com>
---
.../devicetree/bindings/media/st,stm32-dcmi.txt | 77 ++++++++++++++++++++++
1 file changed, 77 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/st,stm32-dcmi.txt
diff --git a/Documentation/devicetree/bindings/media/st,stm32-dcmi.txt b/Documentation/devicetree/bindings/media/st,stm32-dcmi.txt
new file mode 100644
index 0000000..f0dc709
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/st,stm32-dcmi.txt
@@ -0,0 +1,77 @@
+STMicroelectronics STM32 Digital Camera Memory Interface (DCMI)
+
+Required properties:
+- compatible: "st,stm32-dcmi"
+- reg: physical base address and length of the registers set for the device;
+- interrupts: should contain IRQ line for the DCMI;
+- clocks: list of clock specifiers, corresponding to entries in
+ the clock-names property;
+- clock-names: must contain "mclk", which is the DCMI peripherial clock.
+- resets: Reference to a reset controller
+- reset-names: see Documentation/devicetree/bindings/reset/st,stm32-rcc.txt
+
+DCMI supports a single port node with parallel bus. It should contain one
+'port' child node with child 'endpoint' node. Please refer to the bindings
+defined in Documentation/devicetree/bindings/media/video-interfaces.txt.
+
+Example:
+
+Device node example
+-------------------
+ dcmi: dcmi@50050000 {
+ compatible = "st,stm32-dcmi";
+ reg = <0x50050000 0x400>;
+ interrupts = <78>;
+ resets = <&rcc STM32F4_AHB2_RESET(DCMI)>;
+ clocks = <&rcc 0 STM32F4_AHB2_CLOCK(DCMI)>;
+ clock-names = "mclk";
+ pinctrl-names = "default";
+ pinctrl-0 = <&dcmi_pins>;
+ dmas = <&dma2 1 1 0x414 0x3>;
+ dma-names = "tx";
+ status = "disabled";
+ };
+
+Board setup example
+-------------------
+
+&dcmi {
+ status = "okay";
+
+ port {
+ dcmi_0: endpoint@0 {
+ remote-endpoint = <&ov2640_0>;
+ bus-width = <8>;
+ hsync-active = <0>;
+ vsync-active = <0>;
+ pclk-sample = <1>;
+ };
+ };
+};
+
+{
+ [...]
+ i2c@0 {
+ ov2640: camera@30 {
+ compatible = "ovti,ov2640";
+ reg = <0x30>;
+ resetb-gpios = <&stmpegpio 2 GPIO_ACTIVE_HIGH>;
+ pwdn-gpios = <&stmpegpio 0 GPIO_ACTIVE_LOW>;
+ clocks = <&clk_ext_camera>;
+ clock-names = "xvclk";
+ status = "okay";
+
+ port {
+ ov2640_0: endpoint {
+ remote-endpoint = <&dcmi_0>;
+ };
+ };
+ };
+ };
+
+ clk_ext_camera: clk-ext-camera {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ clock-frequency = <24000000>;
+ };
+}
--
1.9.1
^ permalink raw reply related
* [PATCH v1 2/8] [media] stm32-dcmi: STM32 DCMI camera interface driver
From: Hugues Fruchet @ 2017-03-29 13:25 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Maxime Coquelin, Alexandre Torgue,
Mauro Carvalho Chehab, Hans Verkuil
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-media,
Benjamin Gaignard, Yannick Fertre, Hugues Fruchet
In-Reply-To: <1490793926-6477-1-git-send-email-hugues.fruchet@st.com>
This V4L2 subdev driver enables Digital Camera Memory Interface (DCMI)
of STMicroelectronics STM32 SoC series.
Signed-off-by: Yannick Fertre <yannick.fertre@st.com>
Signed-off-by: Hugues Fruchet <hugues.fruchet@st.com>
---
drivers/media/platform/Kconfig | 12 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/stm32/Makefile | 1 +
drivers/media/platform/stm32/stm32-dcmi.c | 1417 +++++++++++++++++++++++++++++
4 files changed, 1432 insertions(+)
create mode 100644 drivers/media/platform/stm32/Makefile
create mode 100644 drivers/media/platform/stm32/stm32-dcmi.c
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index ab0bb48..3421965 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -114,6 +114,18 @@ config VIDEO_S3C_CAMIF
To compile this driver as a module, choose M here: the module
will be called s3c-camif.
+config VIDEO_STM32_DCMI
+ tristate "Digital Camera Memory Interface (DCMI) support"
+ depends on VIDEO_V4L2 && OF && HAS_DMA
+ depends on ARCH_STM32 || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ ---help---
+ This module makes the STM32 Digital Camera Memory Interface (DCMI)
+ available as a v4l2 device.
+
+ To compile this driver as a module, choose M here: the module
+ will be called stm32-dcmi.
+
source "drivers/media/platform/soc_camera/Kconfig"
source "drivers/media/platform/exynos4-is/Kconfig"
source "drivers/media/platform/am437x/Kconfig"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 8959f6e..d747715 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -64,6 +64,8 @@ obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/
obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/
+obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32/
+
ccflags-y += -I$(srctree)/drivers/media/i2c
obj-$(CONFIG_VIDEO_MEDIATEK_VPU) += mtk-vpu/
diff --git a/drivers/media/platform/stm32/Makefile b/drivers/media/platform/stm32/Makefile
new file mode 100644
index 0000000..9b606a7
--- /dev/null
+++ b/drivers/media/platform/stm32/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32-dcmi.o
diff --git a/drivers/media/platform/stm32/stm32-dcmi.c b/drivers/media/platform/stm32/stm32-dcmi.c
new file mode 100644
index 0000000..2f0286b
--- /dev/null
+++ b/drivers/media/platform/stm32/stm32-dcmi.c
@@ -0,0 +1,1417 @@
+/*
+ * Driver for STM32 Digital Camera Memory Interface
+ *
+ * Copyright (C) STMicroelectronics SA 2017
+ * Authors: Yannick Fertre <yannick.fertre@st.com>
+ * Hugues Fruchet <hugues.fruchet@st.com>
+ * for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * This driver is based on atmel_isi.c
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-image-sizes.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-of.h>
+#include <media/videobuf2-dma-contig.h>
+
+#define DRV_NAME "stm32-dcmi"
+
+/* Registers offset for DCMI */
+#define DCMI_CR 0x00 /* Control Register */
+#define DCMI_SR 0x04 /* Status Register */
+#define DCMI_RIS 0x08 /* Raw Interrupt Status register */
+#define DCMI_IER 0x0C /* Interrupt Enable Register */
+#define DCMI_MIS 0x10 /* Masked Interrupt Status register */
+#define DCMI_ICR 0x14 /* Interrupt Clear Register */
+#define DCMI_ESCR 0x18 /* Embedded Synchronization Code Register */
+#define DCMI_ESUR 0x1C /* Embedded Synchronization Unmask Register */
+#define DCMI_CWSTRT 0x20 /* Crop Window STaRT */
+#define DCMI_CWSIZE 0x24 /* Crop Window SIZE */
+#define DCMI_DR 0x28 /* Data Register */
+#define DCMI_IDR 0x2c /* IDentifier Register */
+
+/* Bits definition for control register (DCMI_CR) */
+#define CR_CAPTURE BIT(0)
+#define CR_CM BIT(1)
+#define CR_CROP BIT(2)
+#define CR_JPEG BIT(3)
+#define CR_ESS BIT(4)
+#define CR_PCKPOL BIT(5)
+#define CR_HSPOL BIT(6)
+#define CR_VSPOL BIT(7)
+#define CR_FCRC_0 BIT(8)
+#define CR_FCRC_1 BIT(9)
+#define CR_EDM_0 BIT(10)
+#define CR_EDM_1 BIT(11)
+#define CR_ENABLE BIT(14)
+
+/* Bits definition for status register (DCMI_SR) */
+#define SR_HSYNC BIT(0)
+#define SR_VSYNC BIT(1)
+#define SR_FNE BIT(2)
+
+/*
+ * Bits definition for interrupt registers
+ * (DCMI_RIS, DCMI_IER, DCMI_MIS, DCMI_ICR)
+ */
+#define IT_FRAME BIT(0)
+#define IT_OVR BIT(1)
+#define IT_ERR BIT(2)
+#define IT_VSYNC BIT(3)
+#define IT_LINE BIT(4)
+
+enum state {
+ STOPPED = 0,
+ RUNNING,
+ STOPPING,
+};
+
+#define MAX_BUS_WIDTH 14
+#define MAX_SUPPORT_WIDTH 2048
+#define MAX_SUPPORT_HEIGHT 2048
+#define MIN_SUPPORT_WIDTH 16
+#define MIN_SUPPORT_HEIGHT 16
+#define TIMEOUT_MS 1000
+
+struct dcmi_graph_entity {
+ struct device_node *node;
+
+ struct v4l2_async_subdev asd;
+ struct v4l2_subdev *subdev;
+};
+
+struct dcmi_format {
+ u32 fourcc;
+ u32 mbus_code;
+ u8 bpp;
+};
+
+struct dcmi_buf {
+ struct vb2_v4l2_buffer vb;
+ bool prepared;
+ dma_addr_t paddr;
+ size_t size;
+ struct list_head list;
+};
+
+struct stm32_dcmi {
+ spinlock_t irqlock;
+ struct device *dev;
+ void __iomem *regs;
+ struct resource *res;
+ struct reset_control *rstc;
+ int sequence;
+ struct list_head buffers;
+ struct dcmi_buf *active;
+
+ struct v4l2_device v4l2_dev;
+ struct video_device *vdev;
+ struct v4l2_async_notifier notifier;
+ struct dcmi_graph_entity entity;
+ struct v4l2_format fmt;
+
+ const struct dcmi_format **user_formats;
+ unsigned int num_user_formats;
+ const struct dcmi_format *current_fmt;
+
+ struct mutex lock;
+ struct vb2_queue queue;
+
+ struct v4l2_of_bus_parallel bus;
+ struct completion complete;
+ struct clk *mclk;
+ enum state state;
+ struct dma_chan *dma_chan;
+ dma_cookie_t dma_cookie;
+ u32 misr;
+ int errors_count;
+ int buffers_count;
+};
+
+static inline struct stm32_dcmi *notifier_to_dcmi(struct v4l2_async_notifier *n)
+{
+ return container_of(n, struct stm32_dcmi, notifier);
+}
+
+static inline u32 reg_read(void __iomem *base, u32 reg)
+{
+ return readl_relaxed(base + reg);
+}
+
+static inline void reg_write(void __iomem *base, u32 reg, u32 val)
+{
+ writel_relaxed(val, base + reg);
+}
+
+static inline void reg_set(void __iomem *base, u32 reg, u32 mask)
+{
+ reg_write(base, reg, reg_read(base, reg) | mask);
+}
+
+static inline void reg_clear(void __iomem *base, u32 reg, u32 mask)
+{
+ reg_write(base, reg, reg_read(base, reg) & ~mask);
+}
+
+static int dcmi_start_capture(struct stm32_dcmi *dcmi);
+
+static void dcmi_dma_callback(void *param)
+{
+ struct stm32_dcmi *dcmi = (struct stm32_dcmi *)param;
+ struct dma_chan *chan = dcmi->dma_chan;
+ struct dma_tx_state state;
+ enum dma_status status;
+
+ spin_lock(&dcmi->irqlock);
+
+ /* Check DMA status */
+ status = dmaengine_tx_status(chan, dcmi->dma_cookie, &state);
+
+ switch (status) {
+ case DMA_IN_PROGRESS:
+ dev_dbg(dcmi->dev, "%s: Received DMA_IN_PROGRESS\n", __func__);
+ break;
+ case DMA_PAUSED:
+ dev_err(dcmi->dev, "%s: Received DMA_PAUSED\n", __func__);
+ break;
+ case DMA_ERROR:
+ dev_err(dcmi->dev, "%s: Received DMA_ERROR\n", __func__);
+ break;
+ case DMA_COMPLETE:
+ dev_dbg(dcmi->dev, "%s: Received DMA_COMPLETE\n", __func__);
+
+ if (dcmi->active) {
+ struct dcmi_buf *buf = dcmi->active;
+ struct vb2_v4l2_buffer *vbuf = &dcmi->active->vb;
+
+ vbuf->sequence = dcmi->sequence++;
+ vbuf->field = V4L2_FIELD_NONE;
+ vbuf->vb2_buf.timestamp = ktime_get_ns();
+ vb2_set_plane_payload(&vbuf->vb2_buf, 0, buf->size);
+ vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE);
+ dev_dbg(dcmi->dev, "buffer[%d] done seq=%d\n",
+ vbuf->vb2_buf.index, vbuf->sequence);
+
+ dcmi->buffers_count++;
+ dcmi->active = NULL;
+ }
+
+ /* Restart a new DMA transfer with next buffer */
+ if (dcmi->state == RUNNING) {
+ int ret;
+
+ if (list_empty(&dcmi->buffers)) {
+ dev_err(dcmi->dev, "%s: No more buffer queued, cannot capture buffer",
+ __func__);
+ dcmi->errors_count++;
+ dcmi->active = NULL;
+
+ spin_unlock(&dcmi->irqlock);
+ return;
+ }
+
+ dcmi->active = list_entry(dcmi->buffers.next,
+ struct dcmi_buf, list);
+
+ list_del_init(&dcmi->active->list);
+
+ ret = dcmi_start_capture(dcmi);
+ if (ret) {
+ dev_err(dcmi->dev, "%s: Cannot restart capture on DMA complete",
+ __func__);
+
+ spin_unlock(&dcmi->irqlock);
+ return;
+ }
+
+ /* Enable capture */
+ reg_set(dcmi->regs, DCMI_CR, CR_CAPTURE);
+ }
+
+ break;
+ default:
+ dev_err(dcmi->dev, "%s: Received unknown status\n", __func__);
+ break;
+ }
+
+ spin_unlock(&dcmi->irqlock);
+}
+
+static int dcmi_start_dma(struct stm32_dcmi *dcmi,
+ struct dcmi_buf *buf)
+{
+ struct dma_async_tx_descriptor *desc = NULL;
+ struct dma_slave_config config;
+ int ret;
+
+ memset(&config, 0, sizeof(config));
+
+ config.src_addr = (dma_addr_t)dcmi->res->start + DCMI_DR;
+ config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ config.dst_maxburst = 4;
+
+ /* Configure DMA channel */
+ ret = dmaengine_slave_config(dcmi->dma_chan, &config);
+ if (ret < 0) {
+ dev_err(dcmi->dev, "%s: DMA channel config failed (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* Prepare a DMA transaction */
+ desc = dmaengine_prep_slave_single(dcmi->dma_chan, buf->paddr,
+ buf->size,
+ DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
+ if (!desc) {
+ dev_err(dcmi->dev, "%s: DMA dmaengine_prep_slave_single failed for buffer size %zu\n",
+ __func__, buf->size);
+ return -EINVAL;
+ }
+
+ /* Set completion callback routine for notification */
+ desc->callback = dcmi_dma_callback;
+ desc->callback_param = dcmi;
+
+ /* Push current DMA transaction in the pending queue */
+ dcmi->dma_cookie = dmaengine_submit(desc);
+
+ dma_async_issue_pending(dcmi->dma_chan);
+
+ return 0;
+}
+
+static int dcmi_start_capture(struct stm32_dcmi *dcmi)
+{
+ int ret;
+ struct dcmi_buf *buf = dcmi->active;
+
+ if (!buf)
+ return -EINVAL;
+
+ ret = dcmi_start_dma(dcmi, buf);
+ if (ret) {
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ dcmi->errors_count++;
+ dcmi->active = NULL;
+
+ return ret;
+ }
+
+ /* Enable capture */
+ reg_set(dcmi->regs, DCMI_CR, CR_CAPTURE);
+
+ return 0;
+}
+
+static irqreturn_t dcmi_irq_thread(int irq, void *arg)
+{
+ struct stm32_dcmi *dcmi = arg;
+ int ret;
+
+ spin_lock(&dcmi->irqlock);
+
+ /* Stop capture is required */
+ if (dcmi->state == STOPPING) {
+ reg_clear(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR);
+
+ dcmi->state = STOPPED;
+
+ complete(&dcmi->complete);
+
+ spin_unlock(&dcmi->irqlock);
+ return IRQ_HANDLED;
+ }
+
+ if ((dcmi->misr & IT_OVR) || (dcmi->misr & IT_ERR)) {
+ /*
+ * An overflow or an error has been detected,
+ * stop current DMA transfert & restart it
+ */
+ dev_warn(dcmi->dev, "%s: Overflow or error detected\n",
+ __func__);
+
+ dcmi->errors_count++;
+ dmaengine_terminate_all(dcmi->dma_chan);
+
+ reg_set(dcmi->regs, DCMI_ICR, IT_FRAME | IT_OVR | IT_ERR);
+
+ dev_dbg(dcmi->dev, "Restarting capture after DCMI error\n");
+
+ ret = dcmi_start_capture(dcmi);
+ if (ret) {
+ dev_err(dcmi->dev, "%s: Cannot restart capture on overflow or error\n",
+ __func__);
+
+ spin_unlock(&dcmi->irqlock);
+ return IRQ_HANDLED;
+ }
+ }
+
+ spin_unlock(&dcmi->irqlock);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dcmi_irq_callback(int irq, void *arg)
+{
+ struct stm32_dcmi *dcmi = arg;
+
+ spin_lock(&dcmi->irqlock);
+
+ dcmi->misr = reg_read(dcmi->regs, DCMI_MIS);
+
+ /* Clear interrupt */
+ reg_set(dcmi->regs, DCMI_ICR, IT_FRAME | IT_OVR | IT_ERR);
+
+ spin_unlock(&dcmi->irqlock);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static int dcmi_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers,
+ unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq);
+ unsigned long size;
+
+ size = dcmi->fmt.fmt.pix.sizeimage;
+
+ /* Make sure the image size is large enough */
+ if (*nplanes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ dcmi->active = NULL;
+
+ dev_dbg(dcmi->dev, "Setup queue, count=%d, size=%ld\n",
+ *nbuffers, size);
+
+ return 0;
+}
+
+static int dcmi_buf_init(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb);
+
+ INIT_LIST_HEAD(&buf->list);
+
+ return 0;
+}
+
+static int dcmi_buf_prepare(struct vb2_buffer *vb)
+{
+ struct stm32_dcmi *dcmi = vb2_get_drv_priv(vb->vb2_queue);
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb);
+ unsigned long size;
+
+ size = dcmi->fmt.fmt.pix.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ dev_err(dcmi->dev, "%s data will not fit into plane (%lu < %lu)\n",
+ __func__, vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+
+ if (!buf->prepared) {
+ /* Get memory addresses */
+ buf->paddr =
+ vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+ buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0);
+ buf->prepared = true;
+
+ vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size);
+
+ dev_dbg(dcmi->dev, "buffer[%d] phy=0x%pad size=%zu\n",
+ vb->index, &buf->paddr, buf->size);
+ }
+
+ return 0;
+}
+
+static void dcmi_buf_queue(struct vb2_buffer *vb)
+{
+ struct stm32_dcmi *dcmi = vb2_get_drv_priv(vb->vb2_queue);
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb);
+ unsigned long flags = 0;
+
+ spin_lock_irqsave(&dcmi->irqlock, flags);
+
+ if ((dcmi->state == RUNNING) && (!dcmi->active)) {
+ int ret;
+
+ dcmi->active = buf;
+
+ dev_dbg(dcmi->dev, "Starting capture on buffer[%d] queued\n",
+ buf->vb.vb2_buf.index);
+
+ ret = dcmi_start_capture(dcmi);
+ if (ret) {
+ dev_err(dcmi->dev, "%s: Cannot restart capture on overflow or error\n",
+ __func__);
+
+ spin_unlock_irqrestore(&dcmi->irqlock, flags);
+ return;
+ }
+ } else {
+ /* Enqueue to video buffers list */
+ list_add_tail(&buf->list, &dcmi->buffers);
+ }
+
+ spin_unlock_irqrestore(&dcmi->irqlock, flags);
+}
+
+static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq);
+ struct dcmi_buf *buf, *node;
+ u32 val;
+ int ret;
+
+ /* Enable stream on the sub device */
+ ret = v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 1);
+ if (ret && ret != -ENOIOCTLCMD) {
+ dev_err(dcmi->dev, "%s: Failed to start streaming, subdev streamon error",
+ __func__);
+ goto err_release_buffers;
+ }
+
+ if (clk_enable(dcmi->mclk)) {
+ dev_err(dcmi->dev, "%s: Failed to start streaming, cannot enable clock",
+ __func__);
+ goto err_subdev_streamoff;
+ }
+
+ spin_lock_irq(&dcmi->irqlock);
+
+ val = reg_read(dcmi->regs, DCMI_CR);
+
+ val &= ~(CR_PCKPOL | CR_HSPOL | CR_VSPOL |
+ CR_EDM_0 | CR_EDM_1 | CR_FCRC_0 |
+ CR_FCRC_1 | CR_JPEG | CR_ESS);
+
+ /* Set bus width */
+ switch (dcmi->bus.bus_width) {
+ case 14:
+ val &= CR_EDM_0 + CR_EDM_1;
+ break;
+ case 12:
+ val &= CR_EDM_1;
+ break;
+ case 10:
+ val &= CR_EDM_0;
+ break;
+ default:
+ /* Set bus width to 8 bits by default */
+ break;
+ }
+
+ /* Set vertical synchronization polarity */
+ if (dcmi->bus.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+ val |= CR_VSPOL;
+
+ /* Set horizontal synchronization polarity */
+ if (dcmi->bus.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+ val |= CR_HSPOL;
+
+ /* Set pixel clock polarity */
+ if (dcmi->bus.flags & V4L2_MBUS_PCLK_SAMPLE_RISING)
+ val |= CR_PCKPOL;
+
+ reg_write(dcmi->regs, DCMI_CR, val);
+
+ /* Enable dcmi */
+ reg_set(dcmi->regs, DCMI_CR, CR_ENABLE);
+
+ dcmi->state = RUNNING;
+
+ dcmi->sequence = 0;
+ dcmi->errors_count = 0;
+ dcmi->buffers_count = 0;
+ dcmi->active = NULL;
+
+ /*
+ * Start transfer if at least one buffer has been queued,
+ * otherwise transfer is defered at buffer queueing
+ */
+ if (list_empty(&dcmi->buffers)) {
+ dev_dbg(dcmi->dev, "Start streaming is defered to next buffer queueing\n");
+ spin_unlock_irq(&dcmi->irqlock);
+ return 0;
+ }
+
+ dcmi->active = list_entry(dcmi->buffers.next, struct dcmi_buf, list);
+ list_del_init(&dcmi->active->list);
+
+ /* Enable interruptions */
+ reg_set(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR);
+
+ dev_dbg(dcmi->dev, "Start streaming, starting capture\n");
+
+ ret = dcmi_start_capture(dcmi);
+ if (ret) {
+ dev_err(dcmi->dev, "%s: Start streaming failed, cannot start capture",
+ __func__);
+
+ spin_unlock_irq(&dcmi->irqlock);
+ goto err_subdev_streamoff;
+ }
+
+ spin_unlock_irq(&dcmi->irqlock);
+
+ return 0;
+
+err_subdev_streamoff:
+ v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 0);
+
+err_release_buffers:
+ spin_lock_irq(&dcmi->irqlock);
+ dcmi->active = NULL;
+ /*
+ * return all buffers to vb2 in QUEUED state.
+ * This will give ownership back to userspace
+ */
+ list_for_each_entry_safe(buf, node, &dcmi->buffers, list) {
+ list_del_init(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
+ }
+ spin_unlock_irq(&dcmi->irqlock);
+
+ return ret;
+}
+
+static void dcmi_stop_streaming(struct vb2_queue *vq)
+{
+ struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq);
+ struct dcmi_buf *buf, *node;
+ unsigned long time_ms = msecs_to_jiffies(TIMEOUT_MS);
+ long timeout;
+ int ret;
+
+ /* Disable stream on the sub device */
+ ret = v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 0);
+ if (ret && ret != -ENOIOCTLCMD)
+ dev_err(dcmi->dev, "stream off failed in subdev\n");
+
+ dcmi->state = STOPPING;
+
+ timeout = wait_for_completion_interruptible_timeout(&dcmi->complete,
+ time_ms);
+
+ spin_lock_irq(&dcmi->irqlock);
+
+ /* Disable interruptions */
+ reg_clear(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR);
+
+ /* Disable DCMI */
+ reg_clear(dcmi->regs, DCMI_CR, CR_ENABLE);
+
+ if (!timeout) {
+ dev_err(dcmi->dev, "Timeout during stop streaming\n");
+ dcmi->state = STOPPED;
+ }
+
+ if (dcmi->active) {
+ buf = dcmi->active;
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ dcmi->active = NULL;
+ }
+
+ /* Release all queued buffers */
+ list_for_each_entry_safe(buf, node, &dcmi->buffers, list) {
+ list_del_init(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ spin_unlock_irq(&dcmi->irqlock);
+
+ /* Stop all pending DMA operations */
+ dmaengine_terminate_all(dcmi->dma_chan);
+
+ clk_disable(dcmi->mclk);
+
+ dev_dbg(dcmi->dev, "Stop streaming, errors=%d buffers=%d\n",
+ dcmi->errors_count, dcmi->buffers_count);
+}
+
+static struct vb2_ops dcmi_video_qops = {
+ .queue_setup = dcmi_queue_setup,
+ .buf_init = dcmi_buf_init,
+ .buf_prepare = dcmi_buf_prepare,
+ .buf_queue = dcmi_buf_queue,
+ .start_streaming = dcmi_start_streaming,
+ .stop_streaming = dcmi_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static int dcmi_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct stm32_dcmi *dcmi = video_drvdata(file);
+
+ *fmt = dcmi->fmt;
+
+ return 0;
+}
+
+static const struct dcmi_format *find_format_by_fourcc(struct stm32_dcmi *dcmi,
+ unsigned int fourcc)
+{
+ unsigned int num_formats = dcmi->num_user_formats;
+ const struct dcmi_format *fmt;
+ unsigned int i;
+
+ for (i = 0; i < num_formats; i++) {
+ fmt = dcmi->user_formats[i];
+ if (fmt->fourcc == fourcc)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+static int dcmi_try_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f,
+ const struct dcmi_format **current_fmt)
+{
+ const struct dcmi_format *dcmi_fmt;
+ struct v4l2_pix_format *pixfmt = &f->fmt.pix;
+ struct v4l2_subdev_pad_config pad_cfg;
+ struct v4l2_subdev_format format = {
+ .which = V4L2_SUBDEV_FORMAT_TRY,
+ };
+ int ret;
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ dcmi_fmt = find_format_by_fourcc(dcmi, pixfmt->pixelformat);
+ if (!dcmi_fmt) {
+ dcmi_fmt = dcmi->user_formats[dcmi->num_user_formats - 1];
+ pixfmt->pixelformat = dcmi_fmt->fourcc;
+ }
+
+ /* Limit to hardware capabilities */
+ if (pixfmt->width > MAX_SUPPORT_WIDTH)
+ pixfmt->width = MAX_SUPPORT_WIDTH;
+ if (pixfmt->height > MAX_SUPPORT_HEIGHT)
+ pixfmt->height = MAX_SUPPORT_HEIGHT;
+ if (pixfmt->width < MIN_SUPPORT_WIDTH)
+ pixfmt->width = MIN_SUPPORT_WIDTH;
+ if (pixfmt->height < MIN_SUPPORT_HEIGHT)
+ pixfmt->height = MIN_SUPPORT_HEIGHT;
+
+ v4l2_fill_mbus_format(&format.format, pixfmt, dcmi_fmt->mbus_code);
+ ret = v4l2_subdev_call(dcmi->entity.subdev, pad, set_fmt,
+ &pad_cfg, &format);
+ if (ret < 0)
+ return ret;
+
+ v4l2_fill_pix_format(pixfmt, &format.format);
+
+ pixfmt->field = V4L2_FIELD_NONE;
+ pixfmt->bytesperline = pixfmt->width * dcmi_fmt->bpp;
+ pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height;
+
+ if (current_fmt)
+ *current_fmt = dcmi_fmt;
+
+ return 0;
+}
+
+static int dcmi_set_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f)
+{
+ struct v4l2_subdev_format format = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ const struct dcmi_format *current_fmt;
+ int ret;
+
+ ret = dcmi_try_fmt(dcmi, f, ¤t_fmt);
+ if (ret)
+ return ret;
+
+ v4l2_fill_mbus_format(&format.format, &f->fmt.pix,
+ current_fmt->mbus_code);
+ ret = v4l2_subdev_call(dcmi->entity.subdev, pad,
+ set_fmt, NULL, &format);
+ if (ret < 0)
+ return ret;
+
+ dcmi->fmt = *f;
+ dcmi->current_fmt = current_fmt;
+
+ return 0;
+}
+
+static int dcmi_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct stm32_dcmi *dcmi = video_drvdata(file);
+
+ if (vb2_is_streaming(&dcmi->queue))
+ return -EBUSY;
+
+ return dcmi_set_fmt(dcmi, f);
+}
+
+static int dcmi_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct stm32_dcmi *dcmi = video_drvdata(file);
+
+ return dcmi_try_fmt(dcmi, f, NULL);
+}
+
+static int dcmi_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct stm32_dcmi *dcmi = video_drvdata(file);
+
+ if (f->index >= dcmi->num_user_formats)
+ return -EINVAL;
+
+ f->pixelformat = dcmi->user_formats[f->index]->fourcc;
+ return 0;
+}
+
+static int dcmi_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ strlcpy(cap->driver, DRV_NAME, sizeof(cap->driver));
+ strlcpy(cap->card, "STM32 Digital Camera Memory Interface",
+ sizeof(cap->card));
+ strlcpy(cap->bus_info, "platform:dcmi", sizeof(cap->bus_info));
+ return 0;
+}
+
+static int dcmi_enum_input(struct file *file, void *priv,
+ struct v4l2_input *i)
+{
+ if (i->index != 0)
+ return -EINVAL;
+
+ i->type = V4L2_INPUT_TYPE_CAMERA;
+ strlcpy(i->name, "Camera", sizeof(i->name));
+ return 0;
+}
+
+static int dcmi_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int dcmi_s_input(struct file *file, void *priv, unsigned int i)
+{
+ if (i > 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int dcmi_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct stm32_dcmi *dcmi = video_drvdata(file);
+ const struct dcmi_format *dcmi_fmt;
+ struct v4l2_subdev_frame_size_enum fse = {
+ .index = fsize->index,
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ int ret;
+
+ dcmi_fmt = find_format_by_fourcc(dcmi, fsize->pixel_format);
+ if (!dcmi_fmt)
+ return -EINVAL;
+
+ fse.code = dcmi_fmt->mbus_code;
+
+ ret = v4l2_subdev_call(dcmi->entity.subdev, pad, enum_frame_size,
+ NULL, &fse);
+ if (ret)
+ return ret;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ fsize->discrete.width = fse.max_width;
+ fsize->discrete.height = fse.max_height;
+
+ return 0;
+}
+
+static int dcmi_enum_frameintervals(struct file *file, void *fh,
+ struct v4l2_frmivalenum *fival)
+{
+ struct stm32_dcmi *dcmi = video_drvdata(file);
+ const struct dcmi_format *dcmi_fmt;
+ struct v4l2_subdev_frame_interval_enum fie = {
+ .index = fival->index,
+ .width = fival->width,
+ .height = fival->height,
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ int ret;
+
+ dcmi_fmt = find_format_by_fourcc(dcmi, fival->pixel_format);
+ if (!dcmi_fmt)
+ return -EINVAL;
+
+ fie.code = dcmi_fmt->mbus_code;
+
+ ret = v4l2_subdev_call(dcmi->entity.subdev, pad,
+ enum_frame_interval, NULL, &fie);
+ if (ret)
+ return ret;
+
+ fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ fival->discrete = fie.interval;
+
+ return 0;
+}
+
+static const struct of_device_id stm32_dcmi_of_match[] = {
+ { .compatible = "st,stm32-dcmi"},
+ { /* end node */ },
+};
+MODULE_DEVICE_TABLE(of, stm32_dcmi_of_match);
+
+static int dcmi_open(struct file *file)
+{
+ struct stm32_dcmi *dcmi = video_drvdata(file);
+ struct v4l2_subdev *sd = dcmi->entity.subdev;
+ int ret;
+
+ if (mutex_lock_interruptible(&dcmi->lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto unlock;
+
+ if (!v4l2_fh_is_singular_file(file))
+ goto fh_rel;
+
+ ret = v4l2_subdev_call(sd, core, s_power, 1);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ goto fh_rel;
+
+ ret = dcmi_set_fmt(dcmi, &dcmi->fmt);
+ if (ret)
+ v4l2_subdev_call(sd, core, s_power, 0);
+fh_rel:
+ if (ret)
+ v4l2_fh_release(file);
+unlock:
+ mutex_unlock(&dcmi->lock);
+ return ret;
+}
+
+static int dcmi_release(struct file *file)
+{
+ struct stm32_dcmi *dcmi = video_drvdata(file);
+ struct v4l2_subdev *sd = dcmi->entity.subdev;
+ bool fh_singular;
+ int ret;
+
+ mutex_lock(&dcmi->lock);
+
+ fh_singular = v4l2_fh_is_singular_file(file);
+
+ ret = _vb2_fop_release(file, NULL);
+
+ if (fh_singular)
+ v4l2_subdev_call(sd, core, s_power, 0);
+
+ mutex_unlock(&dcmi->lock);
+
+ return ret;
+}
+
+static const struct v4l2_ioctl_ops dcmi_ioctl_ops = {
+ .vidioc_querycap = dcmi_querycap,
+
+ .vidioc_try_fmt_vid_cap = dcmi_try_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = dcmi_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = dcmi_s_fmt_vid_cap,
+ .vidioc_enum_fmt_vid_cap = dcmi_enum_fmt_vid_cap,
+
+ .vidioc_enum_input = dcmi_enum_input,
+ .vidioc_g_input = dcmi_g_input,
+ .vidioc_s_input = dcmi_s_input,
+
+ .vidioc_enum_framesizes = dcmi_enum_framesizes,
+ .vidioc_enum_frameintervals = dcmi_enum_frameintervals,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations dcmi_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = video_ioctl2,
+ .open = dcmi_open,
+ .release = dcmi_release,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+#ifndef CONFIG_MMU
+ .get_unmapped_area = vb2_fop_get_unmapped_area,
+#endif
+ .read = vb2_fop_read,
+};
+
+static int dcmi_set_default_fmt(struct stm32_dcmi *dcmi)
+{
+ struct v4l2_format f = {
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ .fmt.pix = {
+ .width = CIF_WIDTH,
+ .height = CIF_HEIGHT,
+ .field = V4L2_FIELD_NONE,
+ .pixelformat = dcmi->user_formats[0]->fourcc,
+ },
+ };
+ int ret;
+
+ ret = dcmi_try_fmt(dcmi, &f, NULL);
+ if (ret)
+ return ret;
+ dcmi->current_fmt = dcmi->user_formats[0];
+ dcmi->fmt = f;
+ return 0;
+}
+
+static const struct dcmi_format dcmi_formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE,
+ .bpp = 2,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .bpp = 2,
+ }, {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+ .bpp = 2,
+ },
+};
+
+static int dcmi_formats_init(struct stm32_dcmi *dcmi)
+{
+ const struct dcmi_format *dcmi_fmts[ARRAY_SIZE(dcmi_formats)];
+ unsigned int num_fmts = 0, i, j;
+ struct v4l2_subdev *subdev = dcmi->entity.subdev;
+ struct v4l2_subdev_mbus_code_enum mbus_code = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+
+ while (!v4l2_subdev_call(subdev, pad, enum_mbus_code,
+ NULL, &mbus_code)) {
+ for (i = 0; i < ARRAY_SIZE(dcmi_formats); i++) {
+ if (dcmi_formats[i].mbus_code != mbus_code.code)
+ continue;
+
+ /* Code supported, have we got this fourcc yet? */
+ for (j = 0; j < num_fmts; j++)
+ if (dcmi_fmts[j]->fourcc ==
+ dcmi_formats[i].fourcc)
+ /* Already available */
+ break;
+ if (j == num_fmts)
+ /* New */
+ dcmi_fmts[num_fmts++] = dcmi_formats + i;
+ }
+ mbus_code.index++;
+ }
+
+ if (!num_fmts)
+ return -ENXIO;
+
+ dcmi->num_user_formats = num_fmts;
+ dcmi->user_formats = devm_kcalloc(dcmi->dev,
+ num_fmts, sizeof(struct dcmi_format *),
+ GFP_KERNEL);
+ if (!dcmi->user_formats) {
+ dev_err(dcmi->dev, "could not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ memcpy(dcmi->user_formats, dcmi_fmts,
+ num_fmts * sizeof(struct dcmi_format *));
+ dcmi->current_fmt = dcmi->user_formats[0];
+
+ return 0;
+}
+
+static int dcmi_graph_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier);
+ int ret;
+
+ dcmi->vdev->ctrl_handler = dcmi->entity.subdev->ctrl_handler;
+ ret = dcmi_formats_init(dcmi);
+ if (ret) {
+ dev_err(dcmi->dev, "No supported mediabus format found\n");
+ return ret;
+ }
+
+ ret = dcmi_set_default_fmt(dcmi);
+ if (ret) {
+ dev_err(dcmi->dev, "Could not set default format\n");
+ return ret;
+ }
+
+ ret = video_register_device(dcmi->vdev, VFL_TYPE_GRABBER, -1);
+ if (ret) {
+ dev_err(dcmi->dev, "Failed to register video device\n");
+ return ret;
+ }
+
+ dev_dbg(dcmi->dev, "Device registered as %s\n",
+ video_device_node_name(dcmi->vdev));
+ return 0;
+}
+
+static void dcmi_graph_notify_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_subdev *asd)
+{
+ struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier);
+
+ dev_dbg(dcmi->dev, "Removing %s\n", video_device_node_name(dcmi->vdev));
+
+ /* Checks internaly if vdev has been init or not */
+ video_unregister_device(dcmi->vdev);
+}
+
+static int dcmi_graph_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier);
+
+ dev_dbg(dcmi->dev, "Subdev %s bound\n", subdev->name);
+
+ dcmi->entity.subdev = subdev;
+
+ return 0;
+}
+
+static int dcmi_graph_parse(struct stm32_dcmi *dcmi, struct device_node *node)
+{
+ struct device_node *ep = NULL;
+ struct device_node *remote;
+
+ while (1) {
+ ep = of_graph_get_next_endpoint(node, ep);
+ if (!ep)
+ return -EINVAL;
+
+ remote = of_graph_get_remote_port_parent(ep);
+ if (!remote) {
+ of_node_put(ep);
+ return -EINVAL;
+ }
+
+ /* Remote node to connect */
+ dcmi->entity.node = remote;
+ dcmi->entity.asd.match_type = V4L2_ASYNC_MATCH_OF;
+ dcmi->entity.asd.match.of.node = remote;
+ return 0;
+ }
+}
+
+static int dcmi_graph_init(struct stm32_dcmi *dcmi)
+{
+ struct v4l2_async_subdev **subdevs = NULL;
+ int ret;
+
+ /* Parse the graph to extract a list of subdevice DT nodes. */
+ ret = dcmi_graph_parse(dcmi, dcmi->dev->of_node);
+ if (ret < 0) {
+ dev_err(dcmi->dev, "Graph parsing failed\n");
+ return ret;
+ }
+
+ /* Register the subdevices notifier. */
+ subdevs = devm_kzalloc(dcmi->dev, sizeof(*subdevs), GFP_KERNEL);
+ if (!subdevs) {
+ of_node_put(dcmi->entity.node);
+ return -ENOMEM;
+ }
+
+ subdevs[0] = &dcmi->entity.asd;
+
+ dcmi->notifier.subdevs = subdevs;
+ dcmi->notifier.num_subdevs = 1;
+ dcmi->notifier.bound = dcmi_graph_notify_bound;
+ dcmi->notifier.unbind = dcmi_graph_notify_unbind;
+ dcmi->notifier.complete = dcmi_graph_notify_complete;
+
+ ret = v4l2_async_notifier_register(&dcmi->v4l2_dev, &dcmi->notifier);
+ if (ret < 0) {
+ dev_err(dcmi->dev, "Notifier registration failed\n");
+ of_node_put(dcmi->entity.node);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dcmi_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *match = NULL;
+ struct v4l2_of_endpoint ep;
+ struct stm32_dcmi *dcmi;
+ struct vb2_queue *q;
+ struct dma_chan *chan;
+ struct clk *mclk;
+ int irq;
+ int ret;
+
+ match = of_match_device(of_match_ptr(stm32_dcmi_of_match), &pdev->dev);
+ if (!match) {
+ dev_err(&pdev->dev, "Could not find a match in devicetree\n");
+ return -ENODEV;
+ }
+
+ dcmi = devm_kzalloc(&pdev->dev, sizeof(struct stm32_dcmi), GFP_KERNEL);
+ if (!dcmi)
+ return -ENOMEM;
+
+ dcmi->rstc = of_reset_control_get(np, NULL);
+ if (IS_ERR(dcmi->rstc)) {
+ dev_err(&pdev->dev, "Could not get reset control\n");
+ return -ENODEV;
+ }
+
+ /* Get bus characteristics from devicetree */
+ np = of_graph_get_next_endpoint(np, NULL);
+ if (!np) {
+ dev_err(&pdev->dev, "Could not find the endpoint\n");
+ of_node_put(np);
+ goto err_reset_control_put;
+ }
+
+ ret = v4l2_of_parse_endpoint(np, &ep);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not parse the endpoint\n");
+ of_node_put(np);
+ goto err_reset_control_put;
+ }
+
+ if (ep.bus_type == V4L2_MBUS_CSI2) {
+ dev_err(&pdev->dev, "CSI bus not supported\n");
+ of_node_put(np);
+ goto err_reset_control_put;
+ }
+ dcmi->bus.flags = ep.bus.parallel.flags;
+ dcmi->bus.bus_width = ep.bus.parallel.bus_width;
+ dcmi->bus.data_shift = ep.bus.parallel.data_shift;
+
+ of_node_put(np);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ dev_err(&pdev->dev, "Could not get irq\n");
+ return -ENODEV;
+ }
+
+ dcmi->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!dcmi->res) {
+ dev_err(&pdev->dev, "Could not get resource\n");
+ return -ENODEV;
+ }
+
+ dcmi->regs = devm_ioremap_resource(&pdev->dev, dcmi->res);
+ if (IS_ERR(dcmi->regs)) {
+ dev_err(&pdev->dev, "Could not map registers\n");
+ return PTR_ERR(dcmi->regs);
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, dcmi_irq_callback,
+ dcmi_irq_thread, IRQF_ONESHOT,
+ dev_name(&pdev->dev), dcmi);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to request irq %d\n", irq);
+ return -ENODEV;
+ }
+
+ mclk = devm_clk_get(&pdev->dev, "mclk");
+ if (IS_ERR(mclk)) {
+ dev_err(&pdev->dev, "Unable to get mclk\n");
+ return PTR_ERR(mclk);
+ }
+
+ chan = dma_request_slave_channel(&pdev->dev, "tx");
+ if (!chan) {
+ dev_info(&pdev->dev, "Unable to request DMA channel, defer probing\n");
+ return -EPROBE_DEFER;
+ }
+
+ ret = clk_prepare(mclk);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to prepare mclk %p\n", mclk);
+ goto err_dma_release;
+ }
+
+ spin_lock_init(&dcmi->irqlock);
+ mutex_init(&dcmi->lock);
+ init_completion(&dcmi->complete);
+ INIT_LIST_HEAD(&dcmi->buffers);
+
+ dcmi->dev = &pdev->dev;
+ dcmi->mclk = mclk;
+ dcmi->state = STOPPED;
+ dcmi->dma_chan = chan;
+
+ q = &dcmi->queue;
+
+ /* Initialize the top-level structure */
+ ret = v4l2_device_register(&pdev->dev, &dcmi->v4l2_dev);
+ if (ret)
+ goto err_clk_unprepare;
+
+ dcmi->vdev = video_device_alloc();
+ if (!dcmi->vdev) {
+ ret = -ENOMEM;
+ goto err_device_unregister;
+ }
+
+ /* Video node */
+ dcmi->vdev->fops = &dcmi_fops;
+ dcmi->vdev->v4l2_dev = &dcmi->v4l2_dev;
+ dcmi->vdev->queue = &dcmi->queue;
+ strlcpy(dcmi->vdev->name, KBUILD_MODNAME, sizeof(dcmi->vdev->name));
+ dcmi->vdev->release = video_device_release;
+ dcmi->vdev->ioctl_ops = &dcmi_ioctl_ops;
+ dcmi->vdev->lock = &dcmi->lock;
+ dcmi->vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
+ V4L2_CAP_READWRITE;
+ video_set_drvdata(dcmi->vdev, dcmi);
+
+ /* Buffer queue */
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF;
+ q->lock = &dcmi->lock;
+ q->drv_priv = dcmi;
+ q->buf_struct_size = sizeof(struct dcmi_buf);
+ q->ops = &dcmi_video_qops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->min_buffers_needed = 2;
+ q->dev = &pdev->dev;
+
+ ret = vb2_queue_init(q);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to initialize vb2 queue\n");
+ goto err_device_release;
+ }
+
+ ret = dcmi_graph_init(dcmi);
+ if (ret < 0)
+ goto err_device_release;
+
+ /* Reset device */
+ ret = reset_control_assert(dcmi->rstc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to assert the reset line\n");
+ goto err_device_release;
+ }
+
+ usleep_range(3000, 5000);
+
+ ret = reset_control_deassert(dcmi->rstc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to deassert the reset line\n");
+ goto err_device_release;
+ }
+
+ dev_info(&pdev->dev, "Probe done\n");
+
+ platform_set_drvdata(pdev, dcmi);
+ return 0;
+
+err_reset_control_put:
+ reset_control_put(dcmi->rstc);
+err_device_release:
+ video_device_release(dcmi->vdev);
+err_device_unregister:
+ v4l2_device_unregister(&dcmi->v4l2_dev);
+err_clk_unprepare:
+ clk_unprepare(dcmi->mclk);
+err_dma_release:
+ dma_release_channel(dcmi->dma_chan);
+
+ return ret;
+}
+
+static int dcmi_remove(struct platform_device *pdev)
+{
+ struct stm32_dcmi *dcmi = platform_get_drvdata(pdev);
+
+ v4l2_async_notifier_unregister(&dcmi->notifier);
+ v4l2_device_unregister(&dcmi->v4l2_dev);
+ clk_unprepare(dcmi->mclk);
+ dma_release_channel(dcmi->dma_chan);
+ reset_control_put(dcmi->rstc);
+
+ return 0;
+}
+
+static struct platform_driver stm32_dcmi_driver = {
+ .probe = dcmi_probe,
+ .remove = dcmi_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = of_match_ptr(stm32_dcmi_of_match),
+ },
+};
+
+module_platform_driver(stm32_dcmi_driver);
+
+MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>");
+MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("video");
--
1.9.1
^ permalink raw reply related
* [PATCH v1 3/8] ARM: dts: stm32: Enable DCMI support on STM32F429 MCU
From: Hugues Fruchet @ 2017-03-29 13:25 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Maxime Coquelin, Alexandre Torgue,
Mauro Carvalho Chehab, Hans Verkuil
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-media,
Benjamin Gaignard, Yannick Fertre, Hugues Fruchet
In-Reply-To: <1490793926-6477-1-git-send-email-hugues.fruchet@st.com>
Signed-off-by: Hugues Fruchet <hugues.fruchet@st.com>
---
arch/arm/boot/dts/stm32f429.dtsi | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/arch/arm/boot/dts/stm32f429.dtsi b/arch/arm/boot/dts/stm32f429.dtsi
index ee0da97..e1ff978 100644
--- a/arch/arm/boot/dts/stm32f429.dtsi
+++ b/arch/arm/boot/dts/stm32f429.dtsi
@@ -736,6 +736,29 @@
slew-rate = <3>;
};
};
+
+ dcmi_pins: dcmi_pins@0 {
+ pins {
+ pinmux = <STM32F429_PA4_FUNC_DCMI_HSYNC>,
+ <STM32F429_PB7_FUNC_DCMI_VSYNC>,
+ <STM32F429_PA6_FUNC_DCMI_PIXCLK>,
+ <STM32F429_PC6_FUNC_DCMI_D0>,
+ <STM32F429_PC7_FUNC_DCMI_D1>,
+ <STM32F429_PC8_FUNC_DCMI_D2>,
+ <STM32F429_PC9_FUNC_DCMI_D3>,
+ <STM32F429_PC11_FUNC_DCMI_D4>,
+ <STM32F429_PD3_FUNC_DCMI_D5>,
+ <STM32F429_PB8_FUNC_DCMI_D6>,
+ <STM32F429_PE6_FUNC_DCMI_D7>,
+ <STM32F429_PC10_FUNC_DCMI_D8>,
+ <STM32F429_PC12_FUNC_DCMI_D9>,
+ <STM32F429_PD6_FUNC_DCMI_D10>,
+ <STM32F429_PD2_FUNC_DCMI_D11>;
+ bias-disable;
+ drive-push-pull;
+ slew-rate = <3>;
+ };
+ };
};
rcc: rcc@40023810 {
@@ -805,6 +828,20 @@
status = "disabled";
};
+ dcmi: dcmi@50050000 {
+ compatible = "st,stm32-dcmi";
+ reg = <0x50050000 0x400>;
+ interrupts = <78>;
+ resets = <&rcc STM32F4_AHB2_RESET(DCMI)>;
+ clocks = <&rcc 0 STM32F4_AHB2_CLOCK(DCMI)>;
+ clock-names = "mclk";
+ pinctrl-names = "default";
+ pinctrl-0 = <&dcmi_pins>;
+ dmas = <&dma2 1 1 0x414 0x3>;
+ dma-names = "tx";
+ status = "disabled";
+ };
+
rng: rng@50060800 {
compatible = "st,stm32-rng";
reg = <0x50060800 0x400>;
--
1.9.1
^ permalink raw reply related
* [PATCH v1 4/8] ARM: dts: stm32: Enable DCMI camera interface on STM32F429-EVAL board
From: Hugues Fruchet @ 2017-03-29 13:25 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Maxime Coquelin, Alexandre Torgue,
Mauro Carvalho Chehab, Hans Verkuil
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-media,
Benjamin Gaignard, Yannick Fertre, Hugues Fruchet
In-Reply-To: <1490793926-6477-1-git-send-email-hugues.fruchet@st.com>
Signed-off-by: Hugues Fruchet <hugues.fruchet@st.com>
---
arch/arm/boot/dts/stm32429i-eval.dts | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/arm/boot/dts/stm32429i-eval.dts b/arch/arm/boot/dts/stm32429i-eval.dts
index 3c99466..87733d3 100644
--- a/arch/arm/boot/dts/stm32429i-eval.dts
+++ b/arch/arm/boot/dts/stm32429i-eval.dts
@@ -141,6 +141,15 @@
clock-frequency = <25000000>;
};
+&dcmi {
+ status = "okay";
+
+ port {
+ dcmi_0: endpoint@0 {
+ };
+ };
+};
+
&i2c1 {
pinctrl-0 = <&i2c1_pins>;
pinctrl-names = "default";
--
1.9.1
^ permalink raw reply related
* [PATCH v1 5/8] ARM: dts: stm32: Enable stmpe1600 gpio expandor of STM32F429-EVAL board
From: Hugues Fruchet @ 2017-03-29 13:25 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Maxime Coquelin, Alexandre Torgue,
Mauro Carvalho Chehab, Hans Verkuil
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-media,
Benjamin Gaignard, Yannick Fertre, Hugues Fruchet
In-Reply-To: <1490793926-6477-1-git-send-email-hugues.fruchet@st.com>
Signed-off-by: Hugues Fruchet <hugues.fruchet@st.com>
---
arch/arm/boot/dts/stm32429i-eval.dts | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/arch/arm/boot/dts/stm32429i-eval.dts b/arch/arm/boot/dts/stm32429i-eval.dts
index 87733d3..7ffcf07 100644
--- a/arch/arm/boot/dts/stm32429i-eval.dts
+++ b/arch/arm/boot/dts/stm32429i-eval.dts
@@ -154,6 +154,23 @@
pinctrl-0 = <&i2c1_pins>;
pinctrl-names = "default";
status = "okay";
+
+ stmpe1600: stmpe1600@42 {
+ compatible = "st,stmpe1600";
+ reg = <0x42>;
+ irq-gpio = <&gpioi 8 0>;
+ irq-trigger = <3>;
+ interrupts = <8 3>;
+ interrupt-parent = <&exti>;
+ interrupt-controller;
+ wakeup-source;
+
+ stmpegpio: stmpe_gpio {
+ compatible = "st,stmpe-gpio";
+ gpio-controller;
+ #gpio-cells = <2>;
+ };
+ };
};
&mac {
--
1.9.1
^ permalink raw reply related
* [PATCH v1 6/8] ARM: dts: stm32: Enable ov2640 camera support of STM32F429-EVAL board
From: Hugues Fruchet @ 2017-03-29 13:25 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Maxime Coquelin, Alexandre Torgue,
Mauro Carvalho Chehab, Hans Verkuil
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-media,
Benjamin Gaignard, Yannick Fertre, Hugues Fruchet
In-Reply-To: <1490793926-6477-1-git-send-email-hugues.fruchet@st.com>
Signed-off-by: Hugues Fruchet <hugues.fruchet@st.com>
---
arch/arm/boot/dts/stm32429i-eval.dts | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/arch/arm/boot/dts/stm32429i-eval.dts b/arch/arm/boot/dts/stm32429i-eval.dts
index 7ffcf07..b7d127c 100644
--- a/arch/arm/boot/dts/stm32429i-eval.dts
+++ b/arch/arm/boot/dts/stm32429i-eval.dts
@@ -48,6 +48,7 @@
/dts-v1/;
#include "stm32f429.dtsi"
#include <dt-bindings/input/input.h>
+#include <dt-bindings/gpio/gpio.h>
/ {
model = "STMicroelectronics STM32429i-EVAL board";
@@ -66,6 +67,14 @@
serial0 = &usart1;
};
+ clocks {
+ clk_ext_camera: clk-ext-camera {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ clock-frequency = <24000000>;
+ };
+ };
+
soc {
dma-ranges = <0xc0000000 0x0 0x10000000>;
};
@@ -146,6 +155,11 @@
port {
dcmi_0: endpoint@0 {
+ remote-endpoint = <&ov2640_0>;
+ bus-width = <8>;
+ hsync-active = <0>;
+ vsync-active = <0>;
+ pclk-sample = <1>;
};
};
};
@@ -155,6 +169,22 @@
pinctrl-names = "default";
status = "okay";
+ ov2640: camera@30 {
+ compatible = "ovti,ov2640";
+ reg = <0x30>;
+ resetb-gpios = <&stmpegpio 2 GPIO_ACTIVE_HIGH>;
+ pwdn-gpios = <&stmpegpio 0 GPIO_ACTIVE_LOW>;
+ clocks = <&clk_ext_camera>;
+ clock-names = "xvclk";
+ status = "okay";
+
+ port {
+ ov2640_0: endpoint {
+ remote-endpoint = <&dcmi_0>;
+ };
+ };
+ };
+
stmpe1600: stmpe1600@42 {
compatible = "st,stmpe1600";
reg = <0x42>;
--
1.9.1
^ permalink raw reply related
* [PATCH v1 7/8] ARM: configs: stm32: stmpe 1600 GPIO expandor
From: Hugues Fruchet @ 2017-03-29 13:25 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Maxime Coquelin, Alexandre Torgue,
Mauro Carvalho Chehab, Hans Verkuil
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-media,
Benjamin Gaignard, Yannick Fertre, Hugues Fruchet
In-Reply-To: <1490793926-6477-1-git-send-email-hugues.fruchet@st.com>
Enable STMPE1600 GPIO expandor.
Signed-off-by: Hugues Fruchet <hugues.fruchet@st.com>
---
arch/arm/configs/stm32_defconfig | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/arm/configs/stm32_defconfig b/arch/arm/configs/stm32_defconfig
index a9d8e3c..84adc88 100644
--- a/arch/arm/configs/stm32_defconfig
+++ b/arch/arm/configs/stm32_defconfig
@@ -49,6 +49,8 @@ CONFIG_SERIAL_STM32_CONSOLE=y
# CONFIG_HW_RANDOM is not set
# CONFIG_HWMON is not set
CONFIG_REGULATOR=y
+CONFIG_GPIO_STMPE=y
+CONFIG_MFD_STMPE=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
# CONFIG_USB_SUPPORT is not set
CONFIG_NEW_LEDS=y
--
1.9.1
^ permalink raw reply related
* [PATCH v1 8/8] ARM: configs: stm32: DCMI + OV2640 camera support
From: Hugues Fruchet @ 2017-03-29 13:25 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Maxime Coquelin, Alexandre Torgue,
Mauro Carvalho Chehab, Hans Verkuil
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-media,
Benjamin Gaignard, Yannick Fertre, Hugues Fruchet
In-Reply-To: <1490793926-6477-1-git-send-email-hugues.fruchet@st.com>
Enable DCMI camera interface and OV2640 camera sensor drivers.
Signed-off-by: Hugues Fruchet <hugues.fruchet@st.com>
---
arch/arm/configs/stm32_defconfig | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/arch/arm/configs/stm32_defconfig b/arch/arm/configs/stm32_defconfig
index 84adc88..3f2e4ce 100644
--- a/arch/arm/configs/stm32_defconfig
+++ b/arch/arm/configs/stm32_defconfig
@@ -53,6 +53,13 @@ CONFIG_GPIO_STMPE=y
CONFIG_MFD_STMPE=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
# CONFIG_USB_SUPPORT is not set
+CONFIG_VIDEO_V4L2=y
+CONFIG_MEDIA_SUBDRV_AUTOSELECT=n
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_MEDIA_SUPPORT=y
+CONFIG_MEDIA_CAMERA_SUPPORT=y
+CONFIG_VIDEO_STM32_DCMI=y
+CONFIG_VIDEO_OV2640=y
CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_GPIO=y
--
1.9.1
^ permalink raw reply related
* Re: [PATCH v3 4/7] arm: dts: r7s72100: Add pin controller node
From: jacopo @ 2017-03-29 13:26 UTC (permalink / raw)
To: Chris Brandt
Cc: Jacopo Mondi, geert+renesas@glider.be,
laurent.pinchart@ideasonboard.com, linus.walleij@linaro.org,
robh+dt@kernel.org, mark.rutland@arm.com, linux@armlinux.org.uk,
linux-renesas-soc@vger.kernel.org, linux-gpio@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <SG2PR06MB11651E209BC332719059CC3F8A330@SG2PR06MB1165.apcprd06.prod.outlook.com>
Hi Chris,
On Mon, Mar 27, 2017 at 05:12:04PM +0000, Chris Brandt wrote:
> Hi Jacopo,
>
>
> On Friday, March 24, 2017, Jacopo Mondi
> > + pinctrl: pinctrl@fcfe3000 {
> > + compatible = "renesas,r7s72100-ports";
> > +
> > + #pinctrl-cells = <1>;
> > +
> > + reg = <0xfcfe3000 0x42C0>;
>
> Out of curiosity, why did you change this from 0x4248 to 0x42C0?
>
> In your update for renesas,rza1-pinctrl.txt, for the 'Example', you changed it from 0x4248 to 0x4230 (which Geert pointed out makes more sense), but then in the actual DT file you changed it to 0x42C0. Typo?
>
Yes, type.
Or inability to perform basic mathematical operations.
Not sure yet.
Thanks for spotting this :)
j
>
> Chris
>
^ permalink raw reply
* Re: [RFC 8/8] ARM: dts: imx6qdl-sabresd: Enable fsl,ldo-bypass
From: Leonard Crestez @ 2017-03-29 13:32 UTC (permalink / raw)
To: Lucas Stach
Cc: Mark Brown, Liam Girdwood, Viresh Kumar, Rafael J. Wysocki,
Shawn Guo, Sascha Hauer, Mark Rutland,
devicetree-u79uwXL29TY76Z2rM5mHXA, Anson Huang, Irina Tirdea,
linux-pm-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Herring,
Octavian Purdila, Fabio Estevam, Robin Gong,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1490202830.29056.8.camel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
On Wed, 2017-03-22 at 18:13 +0100, Lucas Stach wrote:
> Am Mittwoch, den 22.03.2017, 18:53 +0200 schrieb Leonard Crestez:
> > This enables LDO bypass by default on the imx6qdl-sabresd boards. New
> > dts files with -ldo suffix are added for users who want to run with LDOs
> > enabled on these boards anyway.
> Given that using LDO bypass affects the device lifetime negatively (see
> AN4724), I think the default should still be to use LDO enabled mode and
> have new DTs for people that desire to shorten the lifetime of the SoC
> for a minimal drop in power consumption.
This was based on what the Freescale BSP does, I don't particularly
object to upstream having a different default. It would be nice for
testing if this ldo-bypass path was enabled by default for some boards
but it's not a very good reason.
I will switch the default.
--
Regards,
Leonard
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH] drm/arcpgu: Get rid of "encoder-slave" property
From: Alexey Brodkin @ 2017-03-29 13:34 UTC (permalink / raw)
To: Liviu.Dudau-5wv7dgnIgG8@public.gmane.org,
robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-snps-arc-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
Eugeniy Paltsev, daniel-/w4YWyX8dFk@public.gmane.org,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org,
airlied-cv59FeDIM0c@public.gmane.org
In-Reply-To: <20170303182102.GP917-A/Nd4k6kWRHZROr8t4l/smS4ubULX0JqMm0uRHvK7Nw@public.gmane.org>
Hi Liviu, Rob,
On Fri, 2017-03-03 at 18:21 +0000, Liviu.Dudau@arm.com wrote:
> On Fri, Mar 03, 2017 at 05:48:19PM +0000, Alexey Brodkin wrote:
> >
> > Hi Liviu,
> >
> > On Fri, 2017-03-03 at 16:28 +0000, Liviu Dudau wrote:
> > >
> > > On Fri, Mar 03, 2017 at 06:19:24PM +0300, Alexey Brodkin wrote:
> > > >
> > > >
> > > > - /* find the encoder node and initialize it */
> > > > - encoder_node = of_parse_phandle(drm->dev->of_node, "encoder-slave", 0);
> > > > - if (encoder_node) {
> > > > - ret = arcpgu_drm_hdmi_init(drm, encoder_node);
> > > > - of_node_put(encoder_node);
> > > > + /* There is only one output port inside each device, find it */
> > > > + port = of_graph_get_next_endpoint(pdev->dev.of_node, NULL);
> > > > +
> > > > + if (port) {
> > > > + if (of_device_is_available(port))
> > > > + encoder = of_graph_get_remote_port_parent(port);
> > > > + of_node_put(port);
> > > > + }
> > >
> > > You must've been looking at some old version. Current version in -next uses
> > > of_graph_get_remote_node() to replace all those lines you have added (see Rob
> > > Herring's series to introduce of_graph_get_remote_node() function)
> >
> > Hm, I'm not on Linus' master tree [1] and so I thought I was quite up to date :)
> > Still I made a check of linux-next and don't see any changes in
> > "drivers/gpu/drm/arm" compared to Linus' tree.
> >
> > [1] https://urldefense.proofpoint.com/v2/url?u=http-3A__git.kernel.org_cgit_linux_kernel_git_torvalds_linux.git_commit_drivers_gpu_drm_arm-3Fid-3D
> > e4563f6ba71792c77aeccb2092cc23149b44e642&d=DwIDaQ&c=DPL6_X_6JkXFx7AXWqB0tg&r=lqdeeSSEes0GFDDl656eViXO7breS55ytWkhpk5R81I&m=SI66ngnnXy33ncb8m5H4La2
> > T1SzSEiiP7hc_XsRahEc&s=uaswjVXcjYDrUosOkO_UpTMqJMWTT-LLPrg5JE6-t-8&e=
> > [2] https://urldefense.proofpoint.com/v2/url?u=http-3A__git.kernel.org_cgit_linux_kernel_git_next_linux-2Dnext.git_commit_drivers_gpu_drm_arm-3Fid
> > -3De4563f6ba71792c77aeccb2092cc23149b44e642&d=DwIDaQ&c=DPL6_X_6JkXFx7AXWqB0tg&r=lqdeeSSEes0GFDDl656eViXO7breS55ytWkhpk5R81I&m=SI66ngnnXy33ncb8m5H4
> > La2T1SzSEiiP7hc_XsRahEc&s=hl9Y6s3K9LwLL1M2WnL3ODax_V-ZRh8k1iTiyctIqU4&e=
> >
> > Could you please clarify which exact tree did you mean?
>
> Sorry, I thought the series got pulled by one of the DRM trees, but it looks like
> I was wrong. I was carrying a private copy in my internal tree, waiting for the
> moment when it got pulled into drm-next or drm-misc-next.
>
> Rob, do you have an update on your series introducing of_graph_get_remote_node() ?
For some reason I cannot find any relevant commits in linux-next tree even today.
Could you please point me to either any random git tree with mentioned above change or
maybe just mailing list where this patch was sent?
I'd like to implement the same fix in ARCPGU and call it a day finally.
-Alexey
^ permalink raw reply
* Re: [PATCH 2/2] mtd: spi-nor: add driver for STM32 quad spi flash controller
From: Ludovic BARRE @ 2017-03-29 13:35 UTC (permalink / raw)
To: Marek Vasut, Cyrille Pitchen
Cc: David Woodhouse, Brian Norris, Boris Brezillon,
Richard Weinberger, Alexandre Torgue, Rob Herring,
linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <28454969-0f56-7752-b087-5e02a1a20c23-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On 03/29/2017 03:09 PM, Marek Vasut wrote:
> On 03/29/2017 02:24 PM, Ludovic BARRE wrote:
>> hi Marek
> Hi!
>
>> thanks for review
>> comment below
>>
>> On 03/29/2017 12:54 PM, Marek Vasut wrote:
>>> On 03/27/2017 02:54 PM, Ludovic Barre wrote:
>>>> From: Ludovic Barre <ludovic.barre-qxv4g6HH51o@public.gmane.org>
>>>>
>>>> The quadspi is a specialized communication interface targeting single,
>>>> dual or quad SPI Flash memories.
>>>>
>>>> It can operate in any of the following modes:
>>>> -indirect mode: all the operations are performed using the quadspi
>>>> registers
>>>> -read memory-mapped mode: the external Flash memory is mapped to the
>>>> microcontroller address space and is seen by the system as if it was
>>>> an internal memory
>>>>
>>>> Signed-off-by: Ludovic Barre <ludovic.barre-qxv4g6HH51o@public.gmane.org>
>>> Hi! I presume this is not compatible with the Cadence QSPI or any other
>>> QSPI controller, huh ?
>> it's not a cadence QSPI, it's specific for stm32 platform
> OK, got it. Didn't ST use Cadence IP somewhere too though ?
> That's probably what confused me, oh well ...
>
>>> Mostly minor nits below ...
>>>
>>>> ---
>>>> drivers/mtd/spi-nor/Kconfig | 7 +
>>>> drivers/mtd/spi-nor/Makefile | 1 +
>>>> drivers/mtd/spi-nor/stm32-quadspi.c | 679
>>>> ++++++++++++++++++++++++++++++++++++
>>>> 3 files changed, 687 insertions(+)
>>>> create mode 100644 drivers/mtd/spi-nor/stm32-quadspi.c
>>> [...]
>>>
>>>> +struct stm32_qspi_cmd {
>>>> + struct {
>>>> + u32 addr_width:8;
>>>> + u32 dummy:8;
>>>> + u32 data:1;
>>>> + } conf;
>>> This could all be u8 ? Why this awful construct ?
>> yes I could replace each u32 by u8
> Yeah, please do .
>
>>>> + u8 opcode;
>>>> + u32 framemode;
>>>> + u32 qspimode;
>>>> + u32 addr;
>>>> + size_t len;
>>>> + void *buf;
>>>> +};
>>>> +
>>>> +static int stm32_qspi_wait_cmd(struct stm32_qspi *qspi)
>>>> +{
>>>> + u32 cr;
>>>> + int err = 0;
>>> Can readl_poll_timeout() or somesuch replace this function ?
>> in fact stm32_qspi_wait_cmd test if the transfer has been complete (TCF
>> flag)
>> else initialize an interrupt completion.
>> like the time may be long I prefer keep the interrupt wait, if you agree.
> I don't really mind if it fits the hardware better and I agree with you
> it does here.
>
>>>> + if (readl_relaxed(qspi->io_base + QUADSPI_SR) & SR_TCF)
>>>> + return 0;
>>>> +
>>>> + reinit_completion(&qspi->cmd_completion);
>>>> + cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
>>>> + writel_relaxed(cr | CR_TCIE, qspi->io_base + QUADSPI_CR);
>>>> +
>>>> + if
>>>> (!wait_for_completion_interruptible_timeout(&qspi->cmd_completion,
>>>> + msecs_to_jiffies(1000)))
>>>> + err = -ETIMEDOUT;
>>>> +
>>>> + writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
>>>> + return err;
>>>> +}
>>>> +
>>>> +static int stm32_qspi_wait_nobusy(struct stm32_qspi *qspi)
>>>> +{
>>>> + u32 sr;
>>>> +
>>>> + return readl_relaxed_poll_timeout(qspi->io_base + QUADSPI_SR, sr,
>>>> + !(sr & SR_BUSY), 10, 100000);
>>>> +}
>>>> +
>>>> +static void stm32_qspi_set_framemode(struct spi_nor *nor,
>>>> + struct stm32_qspi_cmd *cmd, bool read)
>>>> +{
>>>> + u32 dmode = CCR_DMODE_1;
>>>> +
>>>> + cmd->framemode = CCR_IMODE_1;
>>>> +
>>>> + if (read) {
>>>> + switch (nor->flash_read) {
>>>> + case SPI_NOR_NORMAL:
>>>> + case SPI_NOR_FAST:
>>>> + dmode = CCR_DMODE_1;
>>>> + break;
>>>> + case SPI_NOR_DUAL:
>>>> + dmode = CCR_DMODE_2;
>>>> + break;
>>>> + case SPI_NOR_QUAD:
>>>> + dmode = CCR_DMODE_4;
>>>> + break;
>>>> + }
>>>> + }
>>>> +
>>>> + cmd->framemode |= cmd->conf.data ? dmode : 0;
>>>> + cmd->framemode |= cmd->conf.addr_width ? CCR_ADMODE_1 : 0;
>>>> +}
>>>> +
>>>> +static void stm32_qspi_read_fifo(u8 *val, void __iomem *addr)
>>>> +{
>>>> + *val = readb_relaxed(addr);
>>>> +}
>>>> +
>>>> +static void stm32_qspi_write_fifo(u8 *val, void __iomem *addr)
>>>> +{
>>>> + writeb_relaxed(*val, addr);
>>>> +}
>>>> +
>>>> +static int stm32_qspi_tx_poll(struct stm32_qspi *qspi,
>>>> + const struct stm32_qspi_cmd *cmd)
>>>> +{
>>>> + void (*tx_fifo)(u8 *, void __iomem *);
>>>> + u32 len = cmd->len, sr;
>>>> + u8 *buf = cmd->buf;
>>>> + int ret;
>>>> +
>>>> + if (cmd->qspimode == CCR_FMODE_INDW)
>>>> + tx_fifo = stm32_qspi_write_fifo;
>>>> + else
>>>> + tx_fifo = stm32_qspi_read_fifo;
>>>> +
>>>> + while (len--) {
>>>> + ret = readl_relaxed_poll_timeout(qspi->io_base + QUADSPI_SR,
>>>> + sr, (sr & SR_FTF),
>>>> + 10, 30000);
>>> Add macros for those ad-hoc timeouts.
>> I will add 2 defines (for stm32_qspi_wait_nobusy, stm32_qspi_tx_poll)
>> #define STM32_QSPI_FIFO_TIMEOUT_US 30000
>> #define STM32_QSPI_BUSY_TIMEOUT_US 100000
> Super
>
>>>> + if (ret) {
>>>> + dev_err(qspi->dev, "fifo timeout (stat:%#x)\n", sr);
>>>> + break;
>>>> + }
>>>> + tx_fifo(buf++, qspi->io_base + QUADSPI_DR);
>>>> + }
>>>> +
>>>> + return ret;
>>>> +}
>>> [...]
>>>
>>>> +static int stm32_qspi_send(struct stm32_qspi_flash *flash,
>>>> + const struct stm32_qspi_cmd *cmd)
>>>> +{
>>>> + struct stm32_qspi *qspi = flash->qspi;
>>>> + u32 ccr, dcr, cr;
>>>> + int err;
>>>> +
>>>> + err = stm32_qspi_wait_nobusy(qspi);
>>>> + if (err)
>>>> + goto abort;
>>>> +
>>>> + dcr = readl_relaxed(qspi->io_base + QUADSPI_DCR) & ~DCR_FSIZE_MASK;
>>>> + dcr |= DCR_FSIZE(flash->fsize);
>>>> + writel_relaxed(dcr, qspi->io_base + QUADSPI_DCR);
>>>> +
>>>> + cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
>>>> + cr &= ~CR_PRESC_MASK & ~CR_FSEL;
>>>> + cr |= CR_PRESC(flash->presc);
>>>> + cr |= flash->cs ? CR_FSEL : 0;
>>>> + writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
>>>> +
>>>> + if (cmd->conf.data)
>>>> + writel_relaxed(cmd->len - 1, qspi->io_base + QUADSPI_DLR);
>>>> +
>>>> + ccr = cmd->framemode | cmd->qspimode;
>>>> +
>>>> + if (cmd->conf.dummy)
>>>> + ccr |= CCR_DCYC(cmd->conf.dummy);
>>>> +
>>>> + if (cmd->conf.addr_width)
>>>> + ccr |= CCR_ADSIZE(cmd->conf.addr_width - 1);
>>>> +
>>>> + ccr |= CCR_INST(cmd->opcode);
>>>> + writel_relaxed(ccr, qspi->io_base + QUADSPI_CCR);
>>>> +
>>>> + if (cmd->conf.addr_width && cmd->qspimode != CCR_FMODE_MM)
>>>> + writel_relaxed(cmd->addr, qspi->io_base + QUADSPI_AR);
>>>> +
>>>> + err = stm32_qspi_tx(qspi, cmd);
>>>> + if (err)
>>>> + goto abort;
>>>> +
>>>> + if (cmd->qspimode != CCR_FMODE_MM) {
>>>> + err = stm32_qspi_wait_cmd(qspi);
>>>> + if (err)
>>>> + goto abort;
>>>> + writel_relaxed(FCR_CTCF, qspi->io_base + QUADSPI_FCR);
>>>> + }
>>>> +
>>>> + return err;
>>>> +
>>>> +abort:
>>>> + writel_relaxed(readl_relaxed(qspi->io_base + QUADSPI_CR)
>>>> + | CR_ABORT, qspi->io_base + QUADSPI_CR);
>>> Use a helper variable here too.
>> ok
>>>> + dev_err(qspi->dev, "%s abort err:%d\n", __func__, err);
>>>> + return err;
>>>> +}
>>> [...]
>>>
>>>> +static int stm32_qspi_flash_setup(struct stm32_qspi *qspi,
>>>> + struct device_node *np)
>>>> +{
>>>> + u32 width, flash_read, presc, cs_num, max_rate = 0;
>>>> + struct stm32_qspi_flash *flash;
>>>> + struct mtd_info *mtd;
>>>> + int ret;
>>>> +
>>>> + of_property_read_u32(np, "reg", &cs_num);
>>>> + if (cs_num >= STM32_MAX_NORCHIP)
>>>> + return -EINVAL;
>>>> +
>>>> + of_property_read_u32(np, "spi-max-frequency", &max_rate);
>>>> + if (!max_rate)
>>>> + return -EINVAL;
>>>> +
>>>> + presc = DIV_ROUND_UP(qspi->clk_rate, max_rate) - 1;
>>>> +
>>>> + if (of_property_read_u32(np, "spi-rx-bus-width", &width))
>>>> + width = 1;
>>>> +
>>>> + if (width == 4)
>>>> + flash_read = SPI_NOR_QUAD;
>>>> + else if (width == 2)
>>>> + flash_read = SPI_NOR_DUAL;
>>>> + else if (width == 1)
>>>> + flash_read = SPI_NOR_NORMAL;
>>>> + else
>>>> + return -EINVAL;
>>>> +
>>>> + flash = &qspi->flash[cs_num];
>>>> + flash->qspi = qspi;
>>>> + flash->cs = cs_num;
>>>> + flash->presc = presc;
>>>> +
>>>> + flash->nor.dev = qspi->dev;
>>>> + spi_nor_set_flash_node(&flash->nor, np);
>>>> + flash->nor.priv = flash;
>>>> + mtd = &flash->nor.mtd;
>>>> + mtd->priv = &flash->nor;
>>>> +
>>>> + flash->nor.read = stm32_qspi_read;
>>>> + flash->nor.write = stm32_qspi_write;
>>>> + flash->nor.erase = stm32_qspi_erase;
>>>> + flash->nor.read_reg = stm32_qspi_read_reg;
>>>> + flash->nor.write_reg = stm32_qspi_write_reg;
>>>> + flash->nor.prepare = stm32_qspi_prep;
>>>> + flash->nor.unprepare = stm32_qspi_unprep;
>>>> +
>>>> + writel_relaxed(LPTR_DFT_TIMEOUT, qspi->io_base + QUADSPI_LPTR);
>>>> +
>>>> + writel_relaxed(CR_PRESC(presc) | CR_FTHRES(3) | CR_TCEN | CR_SSHIFT
>>>> + | CR_EN, qspi->io_base + QUADSPI_CR);
>>>> +
>>>> + /* a minimum fsize must be set to sent the command id */
>>>> + flash->fsize = 25;
>>> I don't understand why this is needed and the comment doesn't make
>>> sense. Please fix.
>> fsize field defines the size of external memory.
> What external memory ? Unclear
oops, fsize field defined the size of "flash memory" in stm32 qspi
controller.
Number of bytes in Flash memory = 2 ^[FSIZE+1].
To sent a nor cmd this field must be set (hardware issue),
but before "spi_nor_scan" the size of flash nor is not know.
So I set a temporary value (workaround).
After "spi_nor_scan" the fsize is overwritten by the right value
flash->fsize = __fls(mtd->size) - 1;
>> Normaly, this field is used only for memory map mode,
>> but in fact is check in indirect mode.
>> So while flash scan "spi_nor_scan":
>> -I can't let 0.
>> -I not know yet the size of flash.
>> So I fix a temporary value
>>
>> I will update my comment
> Please do, also please consider that I'm reading the comment and I
> barely have any clue about this hardware , so make sure I can understand it.
>
>>>> + ret = spi_nor_scan(&flash->nor, NULL, flash_read);
>>>> + if (ret) {
>>>> + dev_err(qspi->dev, "device scan failed\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + flash->fsize = __fls(mtd->size) - 1;
>>>> +
>>>> + writel_relaxed(DCR_CSHT(1), qspi->io_base + QUADSPI_DCR);
>>>> +
>>>> + ret = mtd_device_register(mtd, NULL, 0);
>>>> + if (ret) {
>>>> + dev_err(qspi->dev, "mtd device parse failed\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + dev_dbg(qspi->dev, "read mm:%s cs:%d bus:%d\n",
>>>> + qspi->read_mode == CCR_FMODE_MM ? "yes" : "no", cs_num, width);
>>>> +
>>>> + return 0;
>>>> +}
>>> [...]
>>>
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 1/2] arc: axs10x: Add DT bindings for I2S audio playback
From: Jose Abreu @ 2017-03-29 13:38 UTC (permalink / raw)
To: Vineet Gupta, robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-snps-arc-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
In-Reply-To: <8b441b9d-bc26-091a-ac48-8594952759e8-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
Hi Vineet, Rob,
On 20-03-2017 14:02, Jose Abreu wrote:
> Hi Vineet, Alexey, Rob,
>
>
> On 02-03-2017 18:57, Alexey Brodkin wrote:
>> Hi Jose,
>>
>> On Wed, 2017-02-22 at 18:19 +0000, Jose Abreu wrote:
>>> This patch adds the necessary DT bindings to get HDMI audio
>>> output in ARC AXS10x SDP. The bindings for I2S controller were
>>> added as well as the bindings for simple audio card.
>>>
>>> Signed-off-by: Jose Abreu <joabreu-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
>>> Cc: Carlos Palminha <palminha-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
>>> Cc: Alexey Brodkin <abrodkin-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
>>> Cc: Rob Herring <robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
>>> Cc: Vineet Gupta <vgupta-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
>>> Cc: devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>>> Cc: linux-snps-arc-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
>>> Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>>> ---
>>> arch/arc/boot/dts/axs10x_mb.dtsi | 22 ++++++++++++++++++----
>>> 1 file changed, 18 insertions(+), 4 deletions(-)
>>>
>>> diff --git a/arch/arc/boot/dts/axs10x_mb.dtsi b/arch/arc/boot/dts/axs10x_mb.dtsi
>>> index d6c1bbc..9d882b1 100644
>>> --- a/arch/arc/boot/dts/axs10x_mb.dtsi
>>> +++ b/arch/arc/boot/dts/axs10x_mb.dtsi
>>> @@ -149,12 +149,13 @@
>>> interrupts = <14>;
>>> };
>>>
>>> - i2c@0x1e000 {
>>> - compatible = "snps,designware-i2c";
>>> + i2s: i2s@1e000 {
>>> + compatible = "snps,designware-i2s";
>>> reg = <0x1e000 0x100>;
>>> - clock-frequency = <400000>;
>>> - clocks = <&i2cclk>;
>>> + clocks = <&i2sclk 0>;
>>> + clock-names = "i2sclk";
>>> interrupts = <15>;
>>> + #sound-dai-cells = <0>;
>>> };
>>>
>>> i2c@0x1f000 {
>>> @@ -174,6 +175,7 @@
>>> adi,input-colorspace = "rgb";
>>> adi,input-clock = "1x";
>>> adi,clock-delay = <0x03>;
>>> + #sound-dai-cells = <0>;
>>>
>>> ports {
>>> #address-cells = <1>;
>>> @@ -295,5 +297,17 @@
>>> };
>>> };
>>> };
>>> +
>>> + sound_playback {
>>> + compatible = "simple-audio-card";
>>> + simple-audio-card,name = "AXS10x HDMI Audio";
>>> + simple-audio-card,format = "i2s";
>>> + simple-audio-card,cpu {
>>> + sound-dai = <&i2s>;
>>> + };
>>> + simple-audio-card,codec {
>>> + sound-dai = <&adv7511>;
>>> + };
>>> + };
>>> };
>>> };
>> Just for the sake of history that's my mods to defconfig that allowed me
>> to play .pcm via HDMI from axs103 board:
>> -------------------------------->8-------------------------------
>> diff --git a/arch/arc/configs/axs103_smp_defconfig b/arch/arc/configs/axs103_smp_defconfig
>> index 30a3d4cf53d2..b11362a32e4e 100644
>> --- a/arch/arc/configs/axs103_smp_defconfig
>> +++ b/arch/arc/configs/axs103_smp_defconfig
>> @@ -67,25 +67,29 @@ CONFIG_MOUSE_PS2_TOUCHKIT=y
>> CONFIG_MOUSE_SERIAL=y
>> CONFIG_MOUSE_SYNAPTICS_USB=y
>> # CONFIG_LEGACY_PTYS is not set
>> -# CONFIG_DEVKMEM is not set
>> CONFIG_SERIAL_8250=y
>> CONFIG_SERIAL_8250_CONSOLE=y
>> CONFIG_SERIAL_8250_DW=y
>> CONFIG_SERIAL_OF_PLATFORM=y
>> # CONFIG_HW_RANDOM is not set
>> -CONFIG_I2C=y
>> CONFIG_I2C_CHARDEV=y
>> CONFIG_I2C_DESIGNWARE_PLATFORM=y
>> # CONFIG_HWMON is not set
>> -CONFIG_DRM=m
>> -CONFIG_DRM_I2C_ADV7511=m
>> -CONFIG_DRM_ARCPGU=m
>> -CONFIG_FB=y
>> +CONFIG_DRM=y
>> +CONFIG_DRM_I2C_ADV7511=y
>> +CONFIG_DRM_I2C_ADV7511_AUDIO=y
>> +CONFIG_DRM_ARCPGU=y
>> CONFIG_FRAMEBUFFER_CONSOLE=y
>> CONFIG_LOGO=y
>> # CONFIG_LOGO_LINUX_MONO is not set
>> # CONFIG_LOGO_LINUX_VGA16 is not set
>> # CONFIG_LOGO_LINUX_CLUT224 is not set
>> +CONFIG_SOUND=y
>> +CONFIG_SND=y
>> +CONFIG_SND_SOC=y
>> +CONFIG_SND_DESIGNWARE_I2S=y
>> +CONFIG_SND_DESIGNWARE_PCM=y
>> +CONFIG_SND_SIMPLE_CARD=y
>> CONFIG_USB_EHCI_HCD=y
>> CONFIG_USB_EHCI_HCD_PLATFORM=y
>> CONFIG_USB_OHCI_HCD=y
>> -------------------------------->8-------------------------------
>>
>> Anyways...
>>
>> Acked-by: Alexey Brodkin <abrodkin-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
> Rob, can you please take a look at this patch and at 2/2?
>
> Alexey, do you still maintain your ack in these patches? (I'm
> asking because I remember you were having no video after patch
> 2/2 of this series). If so, Vineet, can you please pick this up
> and patch 2/2 also?
>
> Best regards,
> Jose Miguel Abreu
>
> _______________________________________________
> linux-snps-arc mailing list
> linux-snps-arc-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
> https://urldefense.proofpoint.com/v2/url?u=http-3A__lists.infradead.org_mailman_listinfo_linux-2Dsnps-2Darc&d=DwICAg&c=DPL6_X_6JkXFx7AXWqB0tg&r=yaVFU4TjGY0gVF8El1uKcisy6TPsyCl9uN7Wsis-qhY&m=05dGD6sUxxlAGDQnPi7nAbNEBEAQb-u7BnZFDdL6TwI&s=NSYlH7VAuvW7bJx6m50UgCpZmyvijw__WBWmn39sh6Y&e=
Gentle ping :)
Best regards,
Jose Miguel Abreu
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v7 1/7] clocksource/drivers/clksrc-evt-probe: Describe with the DT both the clocksource and the clockevent
From: Daniel Lezcano @ 2017-03-29 13:41 UTC (permalink / raw)
To: Mark Rutland
Cc: Rob Herring, Alexander Kochetkov, Heiko Stuebner,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Thomas Gleixner,
Russell King, Caesar Wang, Huang Tao
In-Reply-To: <20170329125713.GH23442@leverpostej>
On Wed, Mar 29, 2017 at 01:57:14PM +0100, Mark Rutland wrote:
> On Wed, Mar 29, 2017 at 02:36:38PM +0200, Daniel Lezcano wrote:
> > On Wed, Mar 29, 2017 at 11:49:11AM +0100, Mark Rutland wrote:
> > > On Wed, Mar 29, 2017 at 11:22:10AM +0200, Daniel Lezcano wrote:
> > > > On Tue, Mar 28, 2017 at 08:51:46PM -0500, Rob Herring wrote:
> > > > > On Wed, Mar 22, 2017 at 06:48:28PM +0300, Alexander Kochetkov wrote:
> >
> > You can have several timers on the system and may want to use the clockevents
> > from one IP block and the clocksource from another IP block. For example, in
> > the case of a bogus clocksource.
>
> I understand this. However, what I was trying to say is that *how* we
> use a particular device should be a software decision. I have a more
> concrete suggestion on that below.
>
> > Moreover there are some drivers with a node for a clocksource and
> > another one for the clockevent, and the driver is assuming the clockevent is
> > defined first and then the clocksource.
> >
> > eg.
> >
> > arch/arc/boot/dts/abilis_tb10x.dtsi:
> >
> > /* TIMER0 with interrupt for clockevent */
> > timer0 {
> > compatible = "snps,arc-timer";
> > interrupts = <3>;
> > interrupt-parent = <&intc>;
> > clocks = <&cpu_clk>;
> > };
> >
> > /* TIMER1 for free running clocksource */
> > timer1 {
> > compatible = "snps,arc-timer";
> > clocks = <&cpu_clk>;
> > };
> >
> > drivers/clocksource/arc_timer.c:
> >
> > static int __init arc_of_timer_init(struct device_node *np)
> > {
> > static int init_count = 0;
> > int ret;
> >
> > if (!init_count) {
> > init_count = 1;
> > ret = arc_clockevent_setup(np);
> > } else {
> > ret = arc_cs_setup_timer1(np);
> > }
> >
> > return ret;
> > }
> >
> > Even if that works, it is a fragile mechanism.
> >
> > So the purpose of these changes is to provide a stronger timer declaration in
> > order to clearly split in the kernel a clocksource and a clockevent
> > initialization.
>
> I agree that this pattern is not nice. However, I think that splitting
> devices as this level makes the problem *worse*.
>
> Users care that they have a clocksource and a clockevent device. They
> do not care *which* particular device is used as either. The comments in
> the DT above are at best misleading.
Agree.
And the driver is assuming the first node is the clockevent and the second one
is the clocksource. If the DT invert these nodes, that breaks the driver.
> What we need is for the kernel to understand that devices can be both
> clockevent and clocksource (perhaps mutually exclusively), such that the
> kernel can decide how to make use of devices.
>
> That way, for the above the kernel can figure out that timer0 could be
> used as clocksource or clockevent, while timer1 can only be used as a
> clocksource due to the lack of an interrupt. Thus, it can choose to use
> timer0 as a clockevent, and timer1 and a clocksource.
Well, 'interrupt' gives an indication the timer can be used as a clockevent and
clocksource, not the clockevent only.
If we take the case of the rockchip, the arm_arch_timer clocksource is stopped
when the CPU is clock gated. So specifically, we don't want to use this
clocksource but we want to use the arch clockevents because they are better.
> > > > > > With this approach, we allow a mechanism to clearly define a clocksource or a
> > > > > > clockevent without aerobatics we can find around in some drivers:
> > > > > > timer-sp804.c, arc-timer.c, dw_apb_timer_of.c, mps2-timer.c,
> > > > > > renesas-ostm.c, time-efm32.c, time-lpc32xx.c.
> > > > >
> > > > > These all already have bindings and work. What problem are you trying to
> > > > > solve other than restructuring Linux?
> > > >
> > > > Yes, there is already the bindings, but that force to do some hackish
> > > > initialization.
> > >
> > > Here, you are forcing hackish DT changes that do not truly describe HW.
> > > How is that better?
> >
> > So if this is hackish DT changes, then the existing DTs should be fixed, no?
>
> Yes.
>
> For the above snippet, the only thing that needs to change is the
> comment.
>
> > > > I would like to give the opportunity to declare separately a clocksource and a
> > > > clockevent, in order to have full control of how this is initialized.
> > >
> > > To me it sounds like what we need is Linux infrastructure that allows
> > > one to register a device as having both clockevent/clocksource
> > > functionality.
> >
> > That was the idea. Create a macro CLOCKEVENT_OF and CLOCKSOURCE_OF both of them
> > calling their respective init routines. And in addition a TIMER_OF doing both
> > CLOCKEVENT_OF and CLOCKSOURCE_OF.
> >
> > It is the DT which does not allow to do this separation.
> >
> > Would be the following approach more acceptable ?
> >
> > 1. Replace all CLOCKSOURCE_OF by TIMER_OF (just renaming)
>
> I am fine with this renaming.
>
> > 2. A node can have a clockevent and|or a clocksource attributes
>
> As above, this should not be in the DT given it's describing a
> (Linux-specific) SW policy and not a HW detail.
>
> So I must disagree with this.
IIUC my discussion with Rob, an attribute is acceptable (btw if
'clocksource'|'clockevent' names are too Linux specific (+1), what
about a more generic name like 'tick' and 'time' ?).
> > 3. The timer_probe pass a flag to the driver's init function, so this one knows
> > if it should invoke the clockevent/clocksource init functions.
> > No attribute defaults to clocksource|clockevent.
> >
> > That would be backward compatible and will let to create drivers with clutch
> > activated device via DT. Also, it will give the opportunity to the existing
> > drivers to change consolidate their initialization routines.
>
> I think that if anything, we need a combined clocksource+clockevent
> device that we register to the core code. That means all
> clocksource/clockevent drivers have a consolidated routine.
>
> Subsequently, core code should determine how specifically to use the
> device (e.g. based on what other devices are registered, and their
> capabilities).
IMO, the core code is complex enough and that may imply more heuristics.
Regarding the number of timers, I do believe it is much better to simply tell
which one we want to use via the DT (assuming the DT is a description of the
desired hardware, not all the available hardware).
--
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCHv6 11/14] ov2640: convert from soc-camera to a standard subdev sensor driver.
From: Hugues FRUCHET @ 2017-03-29 13:42 UTC (permalink / raw)
To: Hans Verkuil, linux-media@vger.kernel.org
Cc: Guennadi Liakhovetski, Songjun Wu, Sakari Ailus,
devicetree@vger.kernel.org, Hans Verkuil
In-Reply-To: <20170328082347.11159-12-hverkuil@xs4all.nl>
Acked-by: Hugues Fruchet <hugues.fruchet@st.com>
Tested successfully on STM324x9I-EVAL evaluation board embedding
an OV2640 camera sensor.
I don't understand the comment around s_power op that has been dropped
(it is there in code), and no problem is observed doing several
open/close, tell me if I miss something.
BR,
Hugues.
On 03/28/2017 10:23 AM, Hans Verkuil wrote:
> From: Hans Verkuil <hans.verkuil@cisco.com>
>
> Convert ov2640 to a standard subdev driver. The soc-camera driver no longer
> uses this driver, so it can safely be converted.
>
> Note: the s_power op has been dropped: this never worked. When the last open()
> is closed, then the power is turned off, and when it is opened again the power
> is turned on again, but the old state isn't restored.
>
> Someone else can figure out in the future how to get this working correctly,
> but I don't want to spend more time on this.
>
> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
> Acked-by: Sakari Ailus <sakari.ailus@linux.intel.com>
> ---
> drivers/media/i2c/Kconfig | 11 ++++
> drivers/media/i2c/Makefile | 1 +
> drivers/media/i2c/{soc_camera => }/ov2640.c | 89 +++++------------------------
> drivers/media/i2c/soc_camera/Kconfig | 6 --
> drivers/media/i2c/soc_camera/Makefile | 1 -
> 5 files changed, 27 insertions(+), 81 deletions(-)
> rename drivers/media/i2c/{soc_camera => }/ov2640.c (94%)
>
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index cee1dae6e014..db2c63f592c5 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -520,6 +520,17 @@ config VIDEO_APTINA_PLL
> config VIDEO_SMIAPP_PLL
> tristate
>
> +config VIDEO_OV2640
> + tristate "OmniVision OV2640 sensor support"
> + depends on VIDEO_V4L2 && I2C && GPIOLIB
> + depends on MEDIA_CAMERA_SUPPORT
> + help
> + This is a Video4Linux2 sensor-level driver for the OmniVision
> + OV2640 camera.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called ov2640.
> +
> config VIDEO_OV2659
> tristate "OmniVision OV2659 sensor support"
> depends on VIDEO_V4L2 && I2C
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 5bc7bbeb5499..50af1e11c85a 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -57,6 +57,7 @@ obj-$(CONFIG_VIDEO_VP27SMPX) += vp27smpx.o
> obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
> obj-$(CONFIG_VIDEO_UPD64031A) += upd64031a.o
> obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o
> +obj-$(CONFIG_VIDEO_OV2640) += ov2640.o
> obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
> obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
> obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
> diff --git a/drivers/media/i2c/soc_camera/ov2640.c b/drivers/media/i2c/ov2640.c
> similarity index 94%
> rename from drivers/media/i2c/soc_camera/ov2640.c
> rename to drivers/media/i2c/ov2640.c
> index b9a0069f5b33..83f88efbce69 100644
> --- a/drivers/media/i2c/soc_camera/ov2640.c
> +++ b/drivers/media/i2c/ov2640.c
> @@ -24,8 +24,8 @@
> #include <linux/v4l2-mediabus.h>
> #include <linux/videodev2.h>
>
> -#include <media/soc_camera.h>
> #include <media/v4l2-clk.h>
> +#include <media/v4l2-device.h>
> #include <media/v4l2-subdev.h>
> #include <media/v4l2-ctrls.h>
> #include <media/v4l2-image-sizes.h>
> @@ -287,7 +287,6 @@ struct ov2640_priv {
> struct v4l2_clk *clk;
> const struct ov2640_win_size *win;
>
> - struct soc_camera_subdev_desc ssdd_dt;
> struct gpio_desc *resetb_gpio;
> struct gpio_desc *pwdn_gpio;
> };
> @@ -677,13 +676,8 @@ static int ov2640_reset(struct i2c_client *client)
> }
>
> /*
> - * soc_camera_ops functions
> + * functions
> */
> -static int ov2640_s_stream(struct v4l2_subdev *sd, int enable)
> -{
> - return 0;
> -}
> -
> static int ov2640_s_ctrl(struct v4l2_ctrl *ctrl)
> {
> struct v4l2_subdev *sd =
> @@ -744,10 +738,16 @@ static int ov2640_s_register(struct v4l2_subdev *sd,
> static int ov2640_s_power(struct v4l2_subdev *sd, int on)
> {
> struct i2c_client *client = v4l2_get_subdevdata(sd);
> - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
> struct ov2640_priv *priv = to_ov2640(client);
>
> - return soc_camera_set_power(&client->dev, ssdd, priv->clk, on);
> + gpiod_direction_output(priv->pwdn_gpio, !on);
> + if (on && priv->resetb_gpio) {
> + /* Active the resetb pin to perform a reset pulse */
> + gpiod_direction_output(priv->resetb_gpio, 1);
> + usleep_range(3000, 5000);
> + gpiod_direction_output(priv->resetb_gpio, 0);
> + }
> + return 0;
> }
>
> /* Select the nearest higher resolution for capture */
> @@ -994,26 +994,6 @@ static struct v4l2_subdev_core_ops ov2640_subdev_core_ops = {
> .s_power = ov2640_s_power,
> };
>
> -static int ov2640_g_mbus_config(struct v4l2_subdev *sd,
> - struct v4l2_mbus_config *cfg)
> -{
> - struct i2c_client *client = v4l2_get_subdevdata(sd);
> - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
> -
> - cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER |
> - V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_HIGH |
> - V4L2_MBUS_DATA_ACTIVE_HIGH;
> - cfg->type = V4L2_MBUS_PARALLEL;
> - cfg->flags = soc_camera_apply_board_flags(ssdd, cfg);
> -
> - return 0;
> -}
> -
> -static struct v4l2_subdev_video_ops ov2640_subdev_video_ops = {
> - .s_stream = ov2640_s_stream,
> - .g_mbus_config = ov2640_g_mbus_config,
> -};
> -
> static const struct v4l2_subdev_pad_ops ov2640_subdev_pad_ops = {
> .enum_mbus_code = ov2640_enum_mbus_code,
> .get_selection = ov2640_get_selection,
> @@ -1023,40 +1003,9 @@ static const struct v4l2_subdev_pad_ops ov2640_subdev_pad_ops = {
>
> static struct v4l2_subdev_ops ov2640_subdev_ops = {
> .core = &ov2640_subdev_core_ops,
> - .video = &ov2640_subdev_video_ops,
> .pad = &ov2640_subdev_pad_ops,
> };
>
> -/* OF probe functions */
> -static int ov2640_hw_power(struct device *dev, int on)
> -{
> - struct i2c_client *client = to_i2c_client(dev);
> - struct ov2640_priv *priv = to_ov2640(client);
> -
> - dev_dbg(&client->dev, "%s: %s the camera\n",
> - __func__, on ? "ENABLE" : "DISABLE");
> -
> - if (priv->pwdn_gpio)
> - gpiod_direction_output(priv->pwdn_gpio, !on);
> -
> - return 0;
> -}
> -
> -static int ov2640_hw_reset(struct device *dev)
> -{
> - struct i2c_client *client = to_i2c_client(dev);
> - struct ov2640_priv *priv = to_ov2640(client);
> -
> - if (priv->resetb_gpio) {
> - /* Active the resetb pin to perform a reset pulse */
> - gpiod_direction_output(priv->resetb_gpio, 1);
> - usleep_range(3000, 5000);
> - gpiod_direction_output(priv->resetb_gpio, 0);
> - }
> -
> - return 0;
> -}
> -
> static int ov2640_probe_dt(struct i2c_client *client,
> struct ov2640_priv *priv)
> {
> @@ -1076,11 +1025,6 @@ static int ov2640_probe_dt(struct i2c_client *client,
> else if (IS_ERR(priv->pwdn_gpio))
> return PTR_ERR(priv->pwdn_gpio);
>
> - /* Initialize the soc_camera_subdev_desc */
> - priv->ssdd_dt.power = ov2640_hw_power;
> - priv->ssdd_dt.reset = ov2640_hw_reset;
> - client->dev.platform_data = &priv->ssdd_dt;
> -
> return 0;
> }
>
> @@ -1091,7 +1035,6 @@ static int ov2640_probe(struct i2c_client *client,
> const struct i2c_device_id *did)
> {
> struct ov2640_priv *priv;
> - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
> struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
> int ret;
>
> @@ -1112,17 +1055,15 @@ static int ov2640_probe(struct i2c_client *client,
> if (IS_ERR(priv->clk))
> return -EPROBE_DEFER;
>
> - if (!ssdd && !client->dev.of_node) {
> + if (!client->dev.of_node) {
> dev_err(&client->dev, "Missing platform_data for driver\n");
> ret = -EINVAL;
> goto err_clk;
> }
>
> - if (!ssdd) {
> - ret = ov2640_probe_dt(client, priv);
> - if (ret)
> - goto err_clk;
> - }
> + ret = ov2640_probe_dt(client, priv);
> + if (ret)
> + goto err_clk;
>
> v4l2_i2c_subdev_init(&priv->subdev, client, &ov2640_subdev_ops);
> v4l2_ctrl_handler_init(&priv->hdl, 2);
> @@ -1190,6 +1131,6 @@ static struct i2c_driver ov2640_i2c_driver = {
>
> module_i2c_driver(ov2640_i2c_driver);
>
> -MODULE_DESCRIPTION("SoC Camera driver for Omni Vision 2640 sensor");
> +MODULE_DESCRIPTION("Driver for Omni Vision 2640 sensor");
> MODULE_AUTHOR("Alberto Panizzo");
> MODULE_LICENSE("GPL v2");
> diff --git a/drivers/media/i2c/soc_camera/Kconfig b/drivers/media/i2c/soc_camera/Kconfig
> index 7704bcf5cc25..96859f37cb1c 100644
> --- a/drivers/media/i2c/soc_camera/Kconfig
> +++ b/drivers/media/i2c/soc_camera/Kconfig
> @@ -41,12 +41,6 @@ config SOC_CAMERA_MT9V022
> help
> This driver supports MT9V022 cameras from Micron
>
> -config SOC_CAMERA_OV2640
> - tristate "ov2640 camera support"
> - depends on SOC_CAMERA && I2C
> - help
> - This is a ov2640 camera driver
> -
> config SOC_CAMERA_OV5642
> tristate "ov5642 camera support"
> depends on SOC_CAMERA && I2C
> diff --git a/drivers/media/i2c/soc_camera/Makefile b/drivers/media/i2c/soc_camera/Makefile
> index 6f994f9353a0..974bdb721dbe 100644
> --- a/drivers/media/i2c/soc_camera/Makefile
> +++ b/drivers/media/i2c/soc_camera/Makefile
> @@ -3,7 +3,6 @@ obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o
> obj-$(CONFIG_SOC_CAMERA_MT9T031) += mt9t031.o
> obj-$(CONFIG_SOC_CAMERA_MT9T112) += mt9t112.o
> obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o
> -obj-$(CONFIG_SOC_CAMERA_OV2640) += ov2640.o
> obj-$(CONFIG_SOC_CAMERA_OV5642) += ov5642.o
> obj-$(CONFIG_SOC_CAMERA_OV6650) += ov6650.o
> obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o
>
^ permalink raw reply
* Re: [PATCHv6 12/14] ov2640: use standard clk and enable it.
From: Hugues FRUCHET @ 2017-03-29 13:42 UTC (permalink / raw)
To: Hans Verkuil, linux-media@vger.kernel.org
Cc: Guennadi Liakhovetski, Songjun Wu, Sakari Ailus,
devicetree@vger.kernel.org, Hans Verkuil
In-Reply-To: <20170328082347.11159-13-hverkuil@xs4all.nl>
Acked-by: Hugues Fruchet <hugues.fruchet@st.com>
Tested successfully on STM324x9I-EVAL evaluation board embedding
an OV2640 camera sensor.
BR,
Hugues.
On 03/28/2017 10:23 AM, Hans Verkuil wrote:
> From: Hans Verkuil <hans.verkuil@cisco.com>
>
> Convert v4l2_clk to normal clk and enable the clock.
>
> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
> Acked-by: Sakari Ailus <sakari.ailus@linux.intel.com>
> ---
> drivers/media/i2c/ov2640.c | 31 ++++++++++++++-----------------
> 1 file changed, 14 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/media/i2c/ov2640.c b/drivers/media/i2c/ov2640.c
> index 83f88efbce69..0445963c5fae 100644
> --- a/drivers/media/i2c/ov2640.c
> +++ b/drivers/media/i2c/ov2640.c
> @@ -16,6 +16,7 @@
> #include <linux/init.h>
> #include <linux/module.h>
> #include <linux/i2c.h>
> +#include <linux/clk.h>
> #include <linux/slab.h>
> #include <linux/delay.h>
> #include <linux/gpio.h>
> @@ -24,7 +25,6 @@
> #include <linux/v4l2-mediabus.h>
> #include <linux/videodev2.h>
>
> -#include <media/v4l2-clk.h>
> #include <media/v4l2-device.h>
> #include <media/v4l2-subdev.h>
> #include <media/v4l2-ctrls.h>
> @@ -284,7 +284,7 @@ struct ov2640_priv {
> struct v4l2_subdev subdev;
> struct v4l2_ctrl_handler hdl;
> u32 cfmt_code;
> - struct v4l2_clk *clk;
> + struct clk *clk;
> const struct ov2640_win_size *win;
>
> struct gpio_desc *resetb_gpio;
> @@ -1051,14 +1051,11 @@ static int ov2640_probe(struct i2c_client *client,
> return -ENOMEM;
> }
>
> - priv->clk = v4l2_clk_get(&client->dev, "xvclk");
> - if (IS_ERR(priv->clk))
> - return -EPROBE_DEFER;
> -
> - if (!client->dev.of_node) {
> - dev_err(&client->dev, "Missing platform_data for driver\n");
> - ret = -EINVAL;
> - goto err_clk;
> + if (client->dev.of_node) {
> + priv->clk = devm_clk_get(&client->dev, "xvclk");
> + if (IS_ERR(priv->clk))
> + return -EPROBE_DEFER;
> + clk_prepare_enable(priv->clk);
> }
>
> ret = ov2640_probe_dt(client, priv);
> @@ -1074,25 +1071,25 @@ static int ov2640_probe(struct i2c_client *client,
> priv->subdev.ctrl_handler = &priv->hdl;
> if (priv->hdl.error) {
> ret = priv->hdl.error;
> - goto err_clk;
> + goto err_hdl;
> }
>
> ret = ov2640_video_probe(client);
> if (ret < 0)
> - goto err_videoprobe;
> + goto err_hdl;
>
> ret = v4l2_async_register_subdev(&priv->subdev);
> if (ret < 0)
> - goto err_videoprobe;
> + goto err_hdl;
>
> dev_info(&adapter->dev, "OV2640 Probed\n");
>
> return 0;
>
> -err_videoprobe:
> +err_hdl:
> v4l2_ctrl_handler_free(&priv->hdl);
> err_clk:
> - v4l2_clk_put(priv->clk);
> + clk_disable_unprepare(priv->clk);
> return ret;
> }
>
> @@ -1101,9 +1098,9 @@ static int ov2640_remove(struct i2c_client *client)
> struct ov2640_priv *priv = to_ov2640(client);
>
> v4l2_async_unregister_subdev(&priv->subdev);
> - v4l2_clk_put(priv->clk);
> - v4l2_device_unregister_subdev(&priv->subdev);
> v4l2_ctrl_handler_free(&priv->hdl);
> + v4l2_device_unregister_subdev(&priv->subdev);
> + clk_disable_unprepare(priv->clk);
> return 0;
> }
>
>
^ permalink raw reply
* Re: [PATCHv6 11/14] ov2640: convert from soc-camera to a standard subdev sensor driver.
From: Hans Verkuil @ 2017-03-29 13:44 UTC (permalink / raw)
To: Hugues FRUCHET, linux-media@vger.kernel.org
Cc: Guennadi Liakhovetski, Songjun Wu, Sakari Ailus,
devicetree@vger.kernel.org, Hans Verkuil
In-Reply-To: <488e156b-2ceb-3895-6bcc-4785b16c93a5@st.com>
On 29/03/17 15:42, Hugues FRUCHET wrote:
> Acked-by: Hugues Fruchet <hugues.fruchet@st.com>
>
> Tested successfully on STM324x9I-EVAL evaluation board embedding
> an OV2640 camera sensor.
>
> I don't understand the comment around s_power op that has been dropped
> (it is there in code), and no problem is observed doing several
> open/close, tell me if I miss something.
Darn, I forgot to remove that comment in the commit log. It's a leftover from
an earlier version.
Regards,
Hans
>
> BR,
> Hugues.
>
> On 03/28/2017 10:23 AM, Hans Verkuil wrote:
>> From: Hans Verkuil <hans.verkuil@cisco.com>
>>
>> Convert ov2640 to a standard subdev driver. The soc-camera driver no longer
>> uses this driver, so it can safely be converted.
>>
>> Note: the s_power op has been dropped: this never worked. When the last open()
>> is closed, then the power is turned off, and when it is opened again the power
>> is turned on again, but the old state isn't restored.
>>
>> Someone else can figure out in the future how to get this working correctly,
>> but I don't want to spend more time on this.
>>
>> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
>> Acked-by: Sakari Ailus <sakari.ailus@linux.intel.com>
>> ---
>> drivers/media/i2c/Kconfig | 11 ++++
>> drivers/media/i2c/Makefile | 1 +
>> drivers/media/i2c/{soc_camera => }/ov2640.c | 89 +++++------------------------
>> drivers/media/i2c/soc_camera/Kconfig | 6 --
>> drivers/media/i2c/soc_camera/Makefile | 1 -
>> 5 files changed, 27 insertions(+), 81 deletions(-)
>> rename drivers/media/i2c/{soc_camera => }/ov2640.c (94%)
>>
>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>> index cee1dae6e014..db2c63f592c5 100644
>> --- a/drivers/media/i2c/Kconfig
>> +++ b/drivers/media/i2c/Kconfig
>> @@ -520,6 +520,17 @@ config VIDEO_APTINA_PLL
>> config VIDEO_SMIAPP_PLL
>> tristate
>>
>> +config VIDEO_OV2640
>> + tristate "OmniVision OV2640 sensor support"
>> + depends on VIDEO_V4L2 && I2C && GPIOLIB
>> + depends on MEDIA_CAMERA_SUPPORT
>> + help
>> + This is a Video4Linux2 sensor-level driver for the OmniVision
>> + OV2640 camera.
>> +
>> + To compile this driver as a module, choose M here: the
>> + module will be called ov2640.
>> +
>> config VIDEO_OV2659
>> tristate "OmniVision OV2659 sensor support"
>> depends on VIDEO_V4L2 && I2C
>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>> index 5bc7bbeb5499..50af1e11c85a 100644
>> --- a/drivers/media/i2c/Makefile
>> +++ b/drivers/media/i2c/Makefile
>> @@ -57,6 +57,7 @@ obj-$(CONFIG_VIDEO_VP27SMPX) += vp27smpx.o
>> obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
>> obj-$(CONFIG_VIDEO_UPD64031A) += upd64031a.o
>> obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o
>> +obj-$(CONFIG_VIDEO_OV2640) += ov2640.o
>> obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
>> obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
>> obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
>> diff --git a/drivers/media/i2c/soc_camera/ov2640.c b/drivers/media/i2c/ov2640.c
>> similarity index 94%
>> rename from drivers/media/i2c/soc_camera/ov2640.c
>> rename to drivers/media/i2c/ov2640.c
>> index b9a0069f5b33..83f88efbce69 100644
>> --- a/drivers/media/i2c/soc_camera/ov2640.c
>> +++ b/drivers/media/i2c/ov2640.c
>> @@ -24,8 +24,8 @@
>> #include <linux/v4l2-mediabus.h>
>> #include <linux/videodev2.h>
>>
>> -#include <media/soc_camera.h>
>> #include <media/v4l2-clk.h>
>> +#include <media/v4l2-device.h>
>> #include <media/v4l2-subdev.h>
>> #include <media/v4l2-ctrls.h>
>> #include <media/v4l2-image-sizes.h>
>> @@ -287,7 +287,6 @@ struct ov2640_priv {
>> struct v4l2_clk *clk;
>> const struct ov2640_win_size *win;
>>
>> - struct soc_camera_subdev_desc ssdd_dt;
>> struct gpio_desc *resetb_gpio;
>> struct gpio_desc *pwdn_gpio;
>> };
>> @@ -677,13 +676,8 @@ static int ov2640_reset(struct i2c_client *client)
>> }
>>
>> /*
>> - * soc_camera_ops functions
>> + * functions
>> */
>> -static int ov2640_s_stream(struct v4l2_subdev *sd, int enable)
>> -{
>> - return 0;
>> -}
>> -
>> static int ov2640_s_ctrl(struct v4l2_ctrl *ctrl)
>> {
>> struct v4l2_subdev *sd =
>> @@ -744,10 +738,16 @@ static int ov2640_s_register(struct v4l2_subdev *sd,
>> static int ov2640_s_power(struct v4l2_subdev *sd, int on)
>> {
>> struct i2c_client *client = v4l2_get_subdevdata(sd);
>> - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
>> struct ov2640_priv *priv = to_ov2640(client);
>>
>> - return soc_camera_set_power(&client->dev, ssdd, priv->clk, on);
>> + gpiod_direction_output(priv->pwdn_gpio, !on);
>> + if (on && priv->resetb_gpio) {
>> + /* Active the resetb pin to perform a reset pulse */
>> + gpiod_direction_output(priv->resetb_gpio, 1);
>> + usleep_range(3000, 5000);
>> + gpiod_direction_output(priv->resetb_gpio, 0);
>> + }
>> + return 0;
>> }
>>
>> /* Select the nearest higher resolution for capture */
>> @@ -994,26 +994,6 @@ static struct v4l2_subdev_core_ops ov2640_subdev_core_ops = {
>> .s_power = ov2640_s_power,
>> };
>>
>> -static int ov2640_g_mbus_config(struct v4l2_subdev *sd,
>> - struct v4l2_mbus_config *cfg)
>> -{
>> - struct i2c_client *client = v4l2_get_subdevdata(sd);
>> - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
>> -
>> - cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER |
>> - V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_HIGH |
>> - V4L2_MBUS_DATA_ACTIVE_HIGH;
>> - cfg->type = V4L2_MBUS_PARALLEL;
>> - cfg->flags = soc_camera_apply_board_flags(ssdd, cfg);
>> -
>> - return 0;
>> -}
>> -
>> -static struct v4l2_subdev_video_ops ov2640_subdev_video_ops = {
>> - .s_stream = ov2640_s_stream,
>> - .g_mbus_config = ov2640_g_mbus_config,
>> -};
>> -
>> static const struct v4l2_subdev_pad_ops ov2640_subdev_pad_ops = {
>> .enum_mbus_code = ov2640_enum_mbus_code,
>> .get_selection = ov2640_get_selection,
>> @@ -1023,40 +1003,9 @@ static const struct v4l2_subdev_pad_ops ov2640_subdev_pad_ops = {
>>
>> static struct v4l2_subdev_ops ov2640_subdev_ops = {
>> .core = &ov2640_subdev_core_ops,
>> - .video = &ov2640_subdev_video_ops,
>> .pad = &ov2640_subdev_pad_ops,
>> };
>>
>> -/* OF probe functions */
>> -static int ov2640_hw_power(struct device *dev, int on)
>> -{
>> - struct i2c_client *client = to_i2c_client(dev);
>> - struct ov2640_priv *priv = to_ov2640(client);
>> -
>> - dev_dbg(&client->dev, "%s: %s the camera\n",
>> - __func__, on ? "ENABLE" : "DISABLE");
>> -
>> - if (priv->pwdn_gpio)
>> - gpiod_direction_output(priv->pwdn_gpio, !on);
>> -
>> - return 0;
>> -}
>> -
>> -static int ov2640_hw_reset(struct device *dev)
>> -{
>> - struct i2c_client *client = to_i2c_client(dev);
>> - struct ov2640_priv *priv = to_ov2640(client);
>> -
>> - if (priv->resetb_gpio) {
>> - /* Active the resetb pin to perform a reset pulse */
>> - gpiod_direction_output(priv->resetb_gpio, 1);
>> - usleep_range(3000, 5000);
>> - gpiod_direction_output(priv->resetb_gpio, 0);
>> - }
>> -
>> - return 0;
>> -}
>> -
>> static int ov2640_probe_dt(struct i2c_client *client,
>> struct ov2640_priv *priv)
>> {
>> @@ -1076,11 +1025,6 @@ static int ov2640_probe_dt(struct i2c_client *client,
>> else if (IS_ERR(priv->pwdn_gpio))
>> return PTR_ERR(priv->pwdn_gpio);
>>
>> - /* Initialize the soc_camera_subdev_desc */
>> - priv->ssdd_dt.power = ov2640_hw_power;
>> - priv->ssdd_dt.reset = ov2640_hw_reset;
>> - client->dev.platform_data = &priv->ssdd_dt;
>> -
>> return 0;
>> }
>>
>> @@ -1091,7 +1035,6 @@ static int ov2640_probe(struct i2c_client *client,
>> const struct i2c_device_id *did)
>> {
>> struct ov2640_priv *priv;
>> - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
>> struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
>> int ret;
>>
>> @@ -1112,17 +1055,15 @@ static int ov2640_probe(struct i2c_client *client,
>> if (IS_ERR(priv->clk))
>> return -EPROBE_DEFER;
>>
>> - if (!ssdd && !client->dev.of_node) {
>> + if (!client->dev.of_node) {
>> dev_err(&client->dev, "Missing platform_data for driver\n");
>> ret = -EINVAL;
>> goto err_clk;
>> }
>>
>> - if (!ssdd) {
>> - ret = ov2640_probe_dt(client, priv);
>> - if (ret)
>> - goto err_clk;
>> - }
>> + ret = ov2640_probe_dt(client, priv);
>> + if (ret)
>> + goto err_clk;
>>
>> v4l2_i2c_subdev_init(&priv->subdev, client, &ov2640_subdev_ops);
>> v4l2_ctrl_handler_init(&priv->hdl, 2);
>> @@ -1190,6 +1131,6 @@ static struct i2c_driver ov2640_i2c_driver = {
>>
>> module_i2c_driver(ov2640_i2c_driver);
>>
>> -MODULE_DESCRIPTION("SoC Camera driver for Omni Vision 2640 sensor");
>> +MODULE_DESCRIPTION("Driver for Omni Vision 2640 sensor");
>> MODULE_AUTHOR("Alberto Panizzo");
>> MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/media/i2c/soc_camera/Kconfig b/drivers/media/i2c/soc_camera/Kconfig
>> index 7704bcf5cc25..96859f37cb1c 100644
>> --- a/drivers/media/i2c/soc_camera/Kconfig
>> +++ b/drivers/media/i2c/soc_camera/Kconfig
>> @@ -41,12 +41,6 @@ config SOC_CAMERA_MT9V022
>> help
>> This driver supports MT9V022 cameras from Micron
>>
>> -config SOC_CAMERA_OV2640
>> - tristate "ov2640 camera support"
>> - depends on SOC_CAMERA && I2C
>> - help
>> - This is a ov2640 camera driver
>> -
>> config SOC_CAMERA_OV5642
>> tristate "ov5642 camera support"
>> depends on SOC_CAMERA && I2C
>> diff --git a/drivers/media/i2c/soc_camera/Makefile b/drivers/media/i2c/soc_camera/Makefile
>> index 6f994f9353a0..974bdb721dbe 100644
>> --- a/drivers/media/i2c/soc_camera/Makefile
>> +++ b/drivers/media/i2c/soc_camera/Makefile
>> @@ -3,7 +3,6 @@ obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o
>> obj-$(CONFIG_SOC_CAMERA_MT9T031) += mt9t031.o
>> obj-$(CONFIG_SOC_CAMERA_MT9T112) += mt9t112.o
>> obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o
>> -obj-$(CONFIG_SOC_CAMERA_OV2640) += ov2640.o
>> obj-$(CONFIG_SOC_CAMERA_OV5642) += ov5642.o
>> obj-$(CONFIG_SOC_CAMERA_OV6650) += ov6650.o
>> obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o
>>
^ permalink raw reply
* Re: [PATCH] drm/arcpgu: Get rid of "encoder-slave" property
From: Liviu.Dudau @ 2017-03-29 13:48 UTC (permalink / raw)
To: Alexey Brodkin
Cc: robh@kernel.org, linux-kernel@vger.kernel.org, robh+dt@kernel.org,
devicetree@vger.kernel.org, linux-snps-arc@lists.infradead.org,
Eugeniy Paltsev, daniel@ffwll.ch, dri-devel@lists.freedesktop.org,
airlied@linux.ie
In-Reply-To: <1490794439.7754.88.camel@synopsys.com>
On Wed, Mar 29, 2017 at 01:34:00PM +0000, Alexey Brodkin wrote:
> Hi Liviu, Rob,
Hi Alexey,
>
> On Fri, 2017-03-03 at 18:21 +0000, Liviu.Dudau@arm.com wrote:
> > On Fri, Mar 03, 2017 at 05:48:19PM +0000, Alexey Brodkin wrote:
> > >
> > > Hi Liviu,
> > >
> > > On Fri, 2017-03-03 at 16:28 +0000, Liviu Dudau wrote:
> > > >
> > > > On Fri, Mar 03, 2017 at 06:19:24PM +0300, Alexey Brodkin wrote:
> > > > >
> > > > >
> > > > > - /* find the encoder node and initialize it */
> > > > > - encoder_node = of_parse_phandle(drm->dev->of_node, "encoder-slave", 0);
> > > > > - if (encoder_node) {
> > > > > - ret = arcpgu_drm_hdmi_init(drm, encoder_node);
> > > > > - of_node_put(encoder_node);
> > > > > + /* There is only one output port inside each device, find it */
> > > > > + port = of_graph_get_next_endpoint(pdev->dev.of_node, NULL);
> > > > > +
> > > > > + if (port) {
> > > > > + if (of_device_is_available(port))
> > > > > + encoder = of_graph_get_remote_port_parent(port);
> > > > > + of_node_put(port);
> > > > > + }
> > > >
> > > > You must've been looking at some old version. Current version in -next uses
> > > > of_graph_get_remote_node() to replace all those lines you have added (see Rob
> > > > Herring's series to introduce of_graph_get_remote_node() function)
> > >
> > > Hm, I'm not on Linus' master tree [1] and so I thought I was quite up to date :)
> > > Still I made a check of linux-next and don't see any changes in
> > > "drivers/gpu/drm/arm" compared to Linus' tree.
> > >
> > > [1] https://urldefense.proofpoint.com/v2/url?u=http-3A__git.kernel.org_cgit_linux_kernel_git_torvalds_linux.git_commit_drivers_gpu_drm_arm-3Fid-3D
> > > e4563f6ba71792c77aeccb2092cc23149b44e642&d=DwIDaQ&c=DPL6_X_6JkXFx7AXWqB0tg&r=lqdeeSSEes0GFDDl656eViXO7breS55ytWkhpk5R81I&m=SI66ngnnXy33ncb8m5H4La2
> > > T1SzSEiiP7hc_XsRahEc&s=uaswjVXcjYDrUosOkO_UpTMqJMWTT-LLPrg5JE6-t-8&e=
> > > [2] https://urldefense.proofpoint.com/v2/url?u=http-3A__git.kernel.org_cgit_linux_kernel_git_next_linux-2Dnext.git_commit_drivers_gpu_drm_arm-3Fid
> > > -3De4563f6ba71792c77aeccb2092cc23149b44e642&d=DwIDaQ&c=DPL6_X_6JkXFx7AXWqB0tg&r=lqdeeSSEes0GFDDl656eViXO7breS55ytWkhpk5R81I&m=SI66ngnnXy33ncb8m5H4
> > > La2T1SzSEiiP7hc_XsRahEc&s=hl9Y6s3K9LwLL1M2WnL3ODax_V-ZRh8k1iTiyctIqU4&e=
> > >
> > > Could you please clarify which exact tree did you mean?
> >
> > Sorry, I thought the series got pulled by one of the DRM trees, but it looks like
> > I was wrong. I was carrying a private copy in my internal tree, waiting for the
> > moment when it got pulled into drm-next or drm-misc-next.
> >
> > Rob, do you have an update on your series introducing of_graph_get_remote_node() ?
>
> For some reason I cannot find any relevant commits in linux-next tree even today.
> Could you please point me to either any random git tree with mentioned above change or
> maybe just mailing list where this patch was sent?
Not sure why Rob hasn't added it to linux-next, but (according to Rob) this is the latest version:
https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git/log/?h=of-graph-helpers
Best regards,
Liviu
>
> I'd like to implement the same fix in ARCPGU and call it a day finally.
>
> -Alexey
--
====================
| I would like to |
| fix the world, |
| but they're not |
| giving me the |
\ source code! /
---------------
¯\_(ツ)_/¯
^ permalink raw reply
* Re: [PATCHv6 11/14] ov2640: convert from soc-camera to a standard subdev sensor driver.
From: Hugues FRUCHET @ 2017-03-29 13:50 UTC (permalink / raw)
To: Hans Verkuil, linux-media-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Cc: Guennadi Liakhovetski, Songjun Wu, Sakari Ailus,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, Hans Verkuil
In-Reply-To: <5623f5dd-88b3-10e6-5703-9400a893a530-qWit8jRvyhVmR6Xm/wNWPw@public.gmane.org>
Y're welcome, thanks Hans for this work, this allows us to drop our
camera interface driver directly based on subdev.
BR,
Hugues.
On 03/29/2017 03:44 PM, Hans Verkuil wrote:
> On 29/03/17 15:42, Hugues FRUCHET wrote:
>> Acked-by: Hugues Fruchet <hugues.fruchet-qxv4g6HH51o@public.gmane.org>
>>
>> Tested successfully on STM324x9I-EVAL evaluation board embedding
>> an OV2640 camera sensor.
>>
>> I don't understand the comment around s_power op that has been dropped
>> (it is there in code), and no problem is observed doing several
>> open/close, tell me if I miss something.
>
> Darn, I forgot to remove that comment in the commit log. It's a leftover from
> an earlier version.
>
> Regards,
>
> Hans
>
>>
>> BR,
>> Hugues.
>>
>> On 03/28/2017 10:23 AM, Hans Verkuil wrote:
>>> From: Hans Verkuil <hans.verkuil-FYB4Gu1CFyUAvxtiuMwx3w@public.gmane.org>
>>>
>>> Convert ov2640 to a standard subdev driver. The soc-camera driver no longer
>>> uses this driver, so it can safely be converted.
>>>
>>> Note: the s_power op has been dropped: this never worked. When the last open()
>>> is closed, then the power is turned off, and when it is opened again the power
>>> is turned on again, but the old state isn't restored.
>>>
>>> Someone else can figure out in the future how to get this working correctly,
>>> but I don't want to spend more time on this.
>>>
>>> Signed-off-by: Hans Verkuil <hans.verkuil-FYB4Gu1CFyUAvxtiuMwx3w@public.gmane.org>
>>> Acked-by: Sakari Ailus <sakari.ailus-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
>>> ---
>>> drivers/media/i2c/Kconfig | 11 ++++
>>> drivers/media/i2c/Makefile | 1 +
>>> drivers/media/i2c/{soc_camera => }/ov2640.c | 89 +++++------------------------
>>> drivers/media/i2c/soc_camera/Kconfig | 6 --
>>> drivers/media/i2c/soc_camera/Makefile | 1 -
>>> 5 files changed, 27 insertions(+), 81 deletions(-)
>>> rename drivers/media/i2c/{soc_camera => }/ov2640.c (94%)
>>>
>>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>>> index cee1dae6e014..db2c63f592c5 100644
>>> --- a/drivers/media/i2c/Kconfig
>>> +++ b/drivers/media/i2c/Kconfig
>>> @@ -520,6 +520,17 @@ config VIDEO_APTINA_PLL
>>> config VIDEO_SMIAPP_PLL
>>> tristate
>>>
>>> +config VIDEO_OV2640
>>> + tristate "OmniVision OV2640 sensor support"
>>> + depends on VIDEO_V4L2 && I2C && GPIOLIB
>>> + depends on MEDIA_CAMERA_SUPPORT
>>> + help
>>> + This is a Video4Linux2 sensor-level driver for the OmniVision
>>> + OV2640 camera.
>>> +
>>> + To compile this driver as a module, choose M here: the
>>> + module will be called ov2640.
>>> +
>>> config VIDEO_OV2659
>>> tristate "OmniVision OV2659 sensor support"
>>> depends on VIDEO_V4L2 && I2C
>>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>>> index 5bc7bbeb5499..50af1e11c85a 100644
>>> --- a/drivers/media/i2c/Makefile
>>> +++ b/drivers/media/i2c/Makefile
>>> @@ -57,6 +57,7 @@ obj-$(CONFIG_VIDEO_VP27SMPX) += vp27smpx.o
>>> obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
>>> obj-$(CONFIG_VIDEO_UPD64031A) += upd64031a.o
>>> obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o
>>> +obj-$(CONFIG_VIDEO_OV2640) += ov2640.o
>>> obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
>>> obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
>>> obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
>>> diff --git a/drivers/media/i2c/soc_camera/ov2640.c b/drivers/media/i2c/ov2640.c
>>> similarity index 94%
>>> rename from drivers/media/i2c/soc_camera/ov2640.c
>>> rename to drivers/media/i2c/ov2640.c
>>> index b9a0069f5b33..83f88efbce69 100644
>>> --- a/drivers/media/i2c/soc_camera/ov2640.c
>>> +++ b/drivers/media/i2c/ov2640.c
>>> @@ -24,8 +24,8 @@
>>> #include <linux/v4l2-mediabus.h>
>>> #include <linux/videodev2.h>
>>>
>>> -#include <media/soc_camera.h>
>>> #include <media/v4l2-clk.h>
>>> +#include <media/v4l2-device.h>
>>> #include <media/v4l2-subdev.h>
>>> #include <media/v4l2-ctrls.h>
>>> #include <media/v4l2-image-sizes.h>
>>> @@ -287,7 +287,6 @@ struct ov2640_priv {
>>> struct v4l2_clk *clk;
>>> const struct ov2640_win_size *win;
>>>
>>> - struct soc_camera_subdev_desc ssdd_dt;
>>> struct gpio_desc *resetb_gpio;
>>> struct gpio_desc *pwdn_gpio;
>>> };
>>> @@ -677,13 +676,8 @@ static int ov2640_reset(struct i2c_client *client)
>>> }
>>>
>>> /*
>>> - * soc_camera_ops functions
>>> + * functions
>>> */
>>> -static int ov2640_s_stream(struct v4l2_subdev *sd, int enable)
>>> -{
>>> - return 0;
>>> -}
>>> -
>>> static int ov2640_s_ctrl(struct v4l2_ctrl *ctrl)
>>> {
>>> struct v4l2_subdev *sd =
>>> @@ -744,10 +738,16 @@ static int ov2640_s_register(struct v4l2_subdev *sd,
>>> static int ov2640_s_power(struct v4l2_subdev *sd, int on)
>>> {
>>> struct i2c_client *client = v4l2_get_subdevdata(sd);
>>> - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
>>> struct ov2640_priv *priv = to_ov2640(client);
>>>
>>> - return soc_camera_set_power(&client->dev, ssdd, priv->clk, on);
>>> + gpiod_direction_output(priv->pwdn_gpio, !on);
>>> + if (on && priv->resetb_gpio) {
>>> + /* Active the resetb pin to perform a reset pulse */
>>> + gpiod_direction_output(priv->resetb_gpio, 1);
>>> + usleep_range(3000, 5000);
>>> + gpiod_direction_output(priv->resetb_gpio, 0);
>>> + }
>>> + return 0;
>>> }
>>>
>>> /* Select the nearest higher resolution for capture */
>>> @@ -994,26 +994,6 @@ static struct v4l2_subdev_core_ops ov2640_subdev_core_ops = {
>>> .s_power = ov2640_s_power,
>>> };
>>>
>>> -static int ov2640_g_mbus_config(struct v4l2_subdev *sd,
>>> - struct v4l2_mbus_config *cfg)
>>> -{
>>> - struct i2c_client *client = v4l2_get_subdevdata(sd);
>>> - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
>>> -
>>> - cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER |
>>> - V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_HIGH |
>>> - V4L2_MBUS_DATA_ACTIVE_HIGH;
>>> - cfg->type = V4L2_MBUS_PARALLEL;
>>> - cfg->flags = soc_camera_apply_board_flags(ssdd, cfg);
>>> -
>>> - return 0;
>>> -}
>>> -
>>> -static struct v4l2_subdev_video_ops ov2640_subdev_video_ops = {
>>> - .s_stream = ov2640_s_stream,
>>> - .g_mbus_config = ov2640_g_mbus_config,
>>> -};
>>> -
>>> static const struct v4l2_subdev_pad_ops ov2640_subdev_pad_ops = {
>>> .enum_mbus_code = ov2640_enum_mbus_code,
>>> .get_selection = ov2640_get_selection,
>>> @@ -1023,40 +1003,9 @@ static const struct v4l2_subdev_pad_ops ov2640_subdev_pad_ops = {
>>>
>>> static struct v4l2_subdev_ops ov2640_subdev_ops = {
>>> .core = &ov2640_subdev_core_ops,
>>> - .video = &ov2640_subdev_video_ops,
>>> .pad = &ov2640_subdev_pad_ops,
>>> };
>>>
>>> -/* OF probe functions */
>>> -static int ov2640_hw_power(struct device *dev, int on)
>>> -{
>>> - struct i2c_client *client = to_i2c_client(dev);
>>> - struct ov2640_priv *priv = to_ov2640(client);
>>> -
>>> - dev_dbg(&client->dev, "%s: %s the camera\n",
>>> - __func__, on ? "ENABLE" : "DISABLE");
>>> -
>>> - if (priv->pwdn_gpio)
>>> - gpiod_direction_output(priv->pwdn_gpio, !on);
>>> -
>>> - return 0;
>>> -}
>>> -
>>> -static int ov2640_hw_reset(struct device *dev)
>>> -{
>>> - struct i2c_client *client = to_i2c_client(dev);
>>> - struct ov2640_priv *priv = to_ov2640(client);
>>> -
>>> - if (priv->resetb_gpio) {
>>> - /* Active the resetb pin to perform a reset pulse */
>>> - gpiod_direction_output(priv->resetb_gpio, 1);
>>> - usleep_range(3000, 5000);
>>> - gpiod_direction_output(priv->resetb_gpio, 0);
>>> - }
>>> -
>>> - return 0;
>>> -}
>>> -
>>> static int ov2640_probe_dt(struct i2c_client *client,
>>> struct ov2640_priv *priv)
>>> {
>>> @@ -1076,11 +1025,6 @@ static int ov2640_probe_dt(struct i2c_client *client,
>>> else if (IS_ERR(priv->pwdn_gpio))
>>> return PTR_ERR(priv->pwdn_gpio);
>>>
>>> - /* Initialize the soc_camera_subdev_desc */
>>> - priv->ssdd_dt.power = ov2640_hw_power;
>>> - priv->ssdd_dt.reset = ov2640_hw_reset;
>>> - client->dev.platform_data = &priv->ssdd_dt;
>>> -
>>> return 0;
>>> }
>>>
>>> @@ -1091,7 +1035,6 @@ static int ov2640_probe(struct i2c_client *client,
>>> const struct i2c_device_id *did)
>>> {
>>> struct ov2640_priv *priv;
>>> - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
>>> struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
>>> int ret;
>>>
>>> @@ -1112,17 +1055,15 @@ static int ov2640_probe(struct i2c_client *client,
>>> if (IS_ERR(priv->clk))
>>> return -EPROBE_DEFER;
>>>
>>> - if (!ssdd && !client->dev.of_node) {
>>> + if (!client->dev.of_node) {
>>> dev_err(&client->dev, "Missing platform_data for driver\n");
>>> ret = -EINVAL;
>>> goto err_clk;
>>> }
>>>
>>> - if (!ssdd) {
>>> - ret = ov2640_probe_dt(client, priv);
>>> - if (ret)
>>> - goto err_clk;
>>> - }
>>> + ret = ov2640_probe_dt(client, priv);
>>> + if (ret)
>>> + goto err_clk;
>>>
>>> v4l2_i2c_subdev_init(&priv->subdev, client, &ov2640_subdev_ops);
>>> v4l2_ctrl_handler_init(&priv->hdl, 2);
>>> @@ -1190,6 +1131,6 @@ static struct i2c_driver ov2640_i2c_driver = {
>>>
>>> module_i2c_driver(ov2640_i2c_driver);
>>>
>>> -MODULE_DESCRIPTION("SoC Camera driver for Omni Vision 2640 sensor");
>>> +MODULE_DESCRIPTION("Driver for Omni Vision 2640 sensor");
>>> MODULE_AUTHOR("Alberto Panizzo");
>>> MODULE_LICENSE("GPL v2");
>>> diff --git a/drivers/media/i2c/soc_camera/Kconfig b/drivers/media/i2c/soc_camera/Kconfig
>>> index 7704bcf5cc25..96859f37cb1c 100644
>>> --- a/drivers/media/i2c/soc_camera/Kconfig
>>> +++ b/drivers/media/i2c/soc_camera/Kconfig
>>> @@ -41,12 +41,6 @@ config SOC_CAMERA_MT9V022
>>> help
>>> This driver supports MT9V022 cameras from Micron
>>>
>>> -config SOC_CAMERA_OV2640
>>> - tristate "ov2640 camera support"
>>> - depends on SOC_CAMERA && I2C
>>> - help
>>> - This is a ov2640 camera driver
>>> -
>>> config SOC_CAMERA_OV5642
>>> tristate "ov5642 camera support"
>>> depends on SOC_CAMERA && I2C
>>> diff --git a/drivers/media/i2c/soc_camera/Makefile b/drivers/media/i2c/soc_camera/Makefile
>>> index 6f994f9353a0..974bdb721dbe 100644
>>> --- a/drivers/media/i2c/soc_camera/Makefile
>>> +++ b/drivers/media/i2c/soc_camera/Makefile
>>> @@ -3,7 +3,6 @@ obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o
>>> obj-$(CONFIG_SOC_CAMERA_MT9T031) += mt9t031.o
>>> obj-$(CONFIG_SOC_CAMERA_MT9T112) += mt9t112.o
>>> obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o
>>> -obj-$(CONFIG_SOC_CAMERA_OV2640) += ov2640.o
>>> obj-$(CONFIG_SOC_CAMERA_OV5642) += ov5642.o
>>> obj-$(CONFIG_SOC_CAMERA_OV6650) += ov6650.o
>>> obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o
>>>
>--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 2/2] mtd: spi-nor: add driver for STM32 quad spi flash controller
From: Marek Vasut @ 2017-03-29 13:57 UTC (permalink / raw)
To: Ludovic BARRE, Cyrille Pitchen
Cc: David Woodhouse, Brian Norris, Boris Brezillon,
Richard Weinberger, Alexandre Torgue, Rob Herring, linux-mtd,
linux-kernel, devicetree
In-Reply-To: <2410fcde-1d1d-57b5-01ee-bd6bc3bc863b@st.com>
On 03/29/2017 03:35 PM, Ludovic BARRE wrote:
[...]
>>>>> + writel_relaxed(CR_PRESC(presc) | CR_FTHRES(3) | CR_TCEN |
>>>>> CR_SSHIFT
>>>>> + | CR_EN, qspi->io_base + QUADSPI_CR);
>>>>> +
>>>>> + /* a minimum fsize must be set to sent the command id */
>>>>> + flash->fsize = 25;
>>>> I don't understand why this is needed and the comment doesn't make
>>>> sense. Please fix.
>>> fsize field defines the size of external memory.
>> What external memory ? Unclear
> oops, fsize field defined the size of "flash memory" in stm32 qspi
> controller.
Errr, now I am totally lost :) Is that some internal SPI NOR ? Shouldn't
the size be coming from DT or something ?
> Number of bytes in Flash memory = 2 ^[FSIZE+1].
> To sent a nor cmd this field must be set (hardware issue),
> but before "spi_nor_scan" the size of flash nor is not know.
> So I set a temporary value (workaround).
Is it needed before the scan ?
> After "spi_nor_scan" the fsize is overwritten by the right value
> flash->fsize = __fls(mtd->size) - 1;
>>> Normaly, this field is used only for memory map mode,
>>> but in fact is check in indirect mode.
>>> So while flash scan "spi_nor_scan":
>>> -I can't let 0.
>>> -I not know yet the size of flash.
>>> So I fix a temporary value
>>>
>>> I will update my comment
>> Please do, also please consider that I'm reading the comment and I
>> barely have any clue about this hardware , so make sure I can
>> understand it.
>>
>>>>> + ret = spi_nor_scan(&flash->nor, NULL, flash_read);
>>>>> + if (ret) {
>>>>> + dev_err(qspi->dev, "device scan failed\n");
>>>>> + return ret;
>>>>> + }
>>>>> +
>>>>> + flash->fsize = __fls(mtd->size) - 1;
>>>>> +
>>>>> + writel_relaxed(DCR_CSHT(1), qspi->io_base + QUADSPI_DCR);
>>>>> +
>>>>> + ret = mtd_device_register(mtd, NULL, 0);
>>>>> + if (ret) {
>>>>> + dev_err(qspi->dev, "mtd device parse failed\n");
>>>>> + return ret;
>>>>> + }
>>>>> +
>>>>> + dev_dbg(qspi->dev, "read mm:%s cs:%d bus:%d\n",
>>>>> + qspi->read_mode == CCR_FMODE_MM ? "yes" : "no", cs_num,
>>>>> width);
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>> [...]
>>>>
>>
>
--
Best regards,
Marek Vasut
^ permalink raw reply
* [PATCH] Add hardware PTP support.
From: Rafal Ozieblo @ 2017-03-29 14:00 UTC (permalink / raw)
To: nicolas.ferre, netdev, linux-kernel, devicetree, linux-arm-kernel,
davem, harinikatakamlinux, harini.katakam, richardcochran,
Andrei.Pistirica
Cc: Rafal Ozieblo
This patch is based on original Harini's patch and Andrei's patch,
implemented in aseparate file to ease the review/maintanance
and integration with other platforms.
In case that macb is compiled as a module, it has been renamed to
cadence-macb.ko to avoid naming confusion in Makefile.
This driver does support GEM-GXL:
- Enable HW time stamp
- Register ptp clock framework
- Initialize PTP related registers
- Updated dma buffer descriptor read/write mechanism
- HW time stamp on the PTP Ethernet packets are received using the
SO_TIMESTAMPING API. Where timers are obtained from the dma buffer
descriptors
- Added tsu_clk to device tree
Note: Patch on net-next, on March 15th.
Signed-off-by: Rafal Ozieblo <rafalo@cadence.com>
---
Documentation/devicetree/bindings/net/macb.txt | 1 +
drivers/net/ethernet/cadence/Kconfig | 10 +-
drivers/net/ethernet/cadence/Makefile | 7 +-
drivers/net/ethernet/cadence/macb.c | 237 ++++++--
drivers/net/ethernet/cadence/macb.h | 176 +++++-
drivers/net/ethernet/cadence/macb_ptp.c | 724 +++++++++++++++++++++++++
6 files changed, 1109 insertions(+), 46 deletions(-)
create mode 100755 drivers/net/ethernet/cadence/macb_ptp.c
diff --git a/Documentation/devicetree/bindings/net/macb.txt b/Documentation/devicetree/bindings/net/macb.txt
index 1506e94..27966ae 100644
--- a/Documentation/devicetree/bindings/net/macb.txt
+++ b/Documentation/devicetree/bindings/net/macb.txt
@@ -22,6 +22,7 @@ Required properties:
Required elements: 'pclk', 'hclk'
Optional elements: 'tx_clk'
Optional elements: 'rx_clk' applies to cdns,zynqmp-gem
+ Optional elements: 'tsu_clk'
- clocks: Phandles to input clocks.
Optional properties for PHY child node:
diff --git a/drivers/net/ethernet/cadence/Kconfig b/drivers/net/ethernet/cadence/Kconfig
index 608bea1..427d65a 100644
--- a/drivers/net/ethernet/cadence/Kconfig
+++ b/drivers/net/ethernet/cadence/Kconfig
@@ -29,7 +29,15 @@ config MACB
support for the MACB/GEM chip.
To compile this driver as a module, choose M here: the module
- will be called macb.
+ will be macb.
+
+config MACB_USE_HWSTAMP
+ bool "Use IEEE 1588 hwstamp"
+ depends on MACB
+ default y
+ imply PTP_1588_CLOCK
+ ---help---
+ Enable IEEE 1588 Precision Time Protocol (PTP) support for MACB.
config MACB_PCI
tristate "Cadence PCI MACB/GEM support"
diff --git a/drivers/net/ethernet/cadence/Makefile b/drivers/net/ethernet/cadence/Makefile
index 4ba7559..a7f6e04 100644
--- a/drivers/net/ethernet/cadence/Makefile
+++ b/drivers/net/ethernet/cadence/Makefile
@@ -1,6 +1,11 @@
#
# Makefile for the Atmel network device drivers.
#
+cadence-macb-y := macb.o
-obj-$(CONFIG_MACB) += macb.o
+ifeq ($(CONFIG_MACB_USE_HWSTAMP),y)
+cadence-macb-y += macb_ptp.o
+endif
+
+obj-$(CONFIG_MACB) += cadence-macb.o
obj-$(CONFIG_MACB_PCI) += macb_pci.o
diff --git a/drivers/net/ethernet/cadence/macb.c b/drivers/net/ethernet/cadence/macb.c
index 30606b1..32af94e 100644
--- a/drivers/net/ethernet/cadence/macb.c
+++ b/drivers/net/ethernet/cadence/macb.c
@@ -79,33 +79,84 @@
#define MACB_HALT_TIMEOUT 1230
/* DMA buffer descriptor might be different size
- * depends on hardware configuration.
+ * depends on hardware configuration:
+ *
+ * 1. dma address width 32 bits:
+ * word 1: 32 bit address of Data Buffer
+ * word 2: control
+ *
+ * 2. dma address width 64 bits:
+ * word 1: 32 bit address of Data Buffer
+ * word 2: control
+ * word 3: upper 32 bit address of Data Buffer
+ * word 4: unused
+ *
+ * 3. dma address width 32 bits with hardware timestamping:
+ * word 1: 32 bit address of Data Buffer
+ * word 2: control
+ * word 3: timestamp word 1
+ * word 4: timestamp word 2
+ *
+ * 4. dma address width 64 bits with hardware timestamping:
+ * word 1: 32 bit address of Data Buffer
+ * word 2: control
+ * word 3: upper 32 bit address of Data Buffer
+ * word 4: unused
+ * word 5: timestamp word 1
+ * word 6: timestamp word 2
*/
static unsigned int macb_dma_desc_get_size(struct macb *bp)
{
-#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
- if (bp->hw_dma_cap == HW_DMA_CAP_64B)
- return sizeof(struct macb_dma_desc) + sizeof(struct macb_dma_desc_64);
+#ifdef MACB_EXT_DESC
+ unsigned int desc_size;
+
+ switch (bp->hw_dma_cap) {
+ case HW_DMA_CAP_64B:
+ desc_size = sizeof(struct macb_dma_desc)
+ + sizeof(struct macb_dma_desc_64);
+ break;
+ case HW_DMA_CAP_PTP:
+ desc_size = sizeof(struct macb_dma_desc)
+ + sizeof(struct macb_dma_desc_ptp);
+ break;
+ case HW_DMA_CAP_64B_PTP:
+ desc_size = sizeof(struct macb_dma_desc)
+ + sizeof(struct macb_dma_desc_64)
+ + sizeof(struct macb_dma_desc_ptp);
+ break;
+ default:
+ desc_size = sizeof(struct macb_dma_desc);
+ }
+ return desc_size;
#endif
return sizeof(struct macb_dma_desc);
}
-static unsigned int macb_adj_dma_desc_idx(struct macb *bp, unsigned int idx)
+static unsigned int macb_adj_dma_desc_idx(struct macb *bp, unsigned int desc_idx)
{
-#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
- /* Dma buffer descriptor is 4 words length (instead of 2 words)
- * for 64b GEM.
- */
- if (bp->hw_dma_cap == HW_DMA_CAP_64B)
- idx <<= 1;
+#ifdef MACB_EXT_DESC
+ switch (bp->hw_dma_cap) {
+ case HW_DMA_CAP_64B:
+ case HW_DMA_CAP_PTP:
+ desc_idx <<= 1;
+ break;
+ case HW_DMA_CAP_64B_PTP:
+ desc_idx *= 3;
+ break;
+ default:
+ break;
+ }
+ return desc_idx;
#endif
- return idx;
+ return desc_idx;
}
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
static struct macb_dma_desc_64 *macb_64b_desc(struct macb *bp, struct macb_dma_desc *desc)
{
- return (struct macb_dma_desc_64 *)((void *)desc + sizeof(struct macb_dma_desc));
+ if (bp->hw_dma_cap & HW_DMA_CAP_64B)
+ return (struct macb_dma_desc_64 *)((void *)desc + sizeof(struct macb_dma_desc));
+ return NULL;
}
#endif
@@ -600,7 +651,7 @@ static void macb_set_addr(struct macb *bp, struct macb_dma_desc *desc, dma_addr_
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
struct macb_dma_desc_64 *desc_64;
- if (bp->hw_dma_cap == HW_DMA_CAP_64B) {
+ if (bp->hw_dma_cap & HW_DMA_CAP_64B) {
desc_64 = macb_64b_desc(bp, desc);
desc_64->addrh = upper_32_bits(addr);
}
@@ -614,7 +665,7 @@ static dma_addr_t macb_get_addr(struct macb *bp, struct macb_dma_desc *desc)
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
struct macb_dma_desc_64 *desc_64;
- if (bp->hw_dma_cap == HW_DMA_CAP_64B) {
+ if (bp->hw_dma_cap & HW_DMA_CAP_64B) {
desc_64 = macb_64b_desc(bp, desc);
addr = ((u64)(desc_64->addrh) << 32);
}
@@ -713,7 +764,7 @@ static void macb_tx_error_task(struct work_struct *work)
/* Reinitialize the TX desc queue */
queue_writel(queue, TBQP, lower_32_bits(queue->tx_ring_dma));
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
- if (bp->hw_dma_cap == HW_DMA_CAP_64B)
+ if (bp->hw_dma_cap & HW_DMA_CAP_64B)
queue_writel(queue, TBQPH, upper_32_bits(queue->tx_ring_dma));
#endif
/* Make TX ring reflect state of hardware */
@@ -775,6 +826,16 @@ static void macb_tx_interrupt(struct macb_queue *queue)
/* First, update TX stats if needed */
if (skb) {
+#ifdef CONFIG_MACB_USE_HWSTAMP
+ if (gem_ptp_do_txstamp(queue, skb, desc) == 0) {
+ /* skb now belongs to timestamp buffer
+ * and will be removed later
+ */
+ tx_skb->skb = NULL;
+ schedule_work(&queue->tx_ts_task);
+
+ }
+#endif
netdev_vdbg(bp->dev, "skb %u (data %p) TX complete\n",
macb_tx_ring_wrap(bp, tail),
skb->data);
@@ -941,6 +1002,10 @@ static int gem_rx(struct macb *bp, int budget)
bp->stats.rx_packets++;
bp->stats.rx_bytes += skb->len;
+#ifdef CONFIG_MACB_USE_HWSTAMP
+ gem_ptp_do_rxstamp(bp, skb, desc);
+#endif
+
#if defined(DEBUG) && defined(VERBOSE_DEBUG)
netdev_vdbg(bp->dev, "received skb of length %u, csum: %08x\n",
skb->len, skb->csum);
@@ -1263,6 +1328,11 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id)
queue_writel(queue, ISR, MACB_BIT(HRESP));
}
+#ifdef CONFIG_MACB_USE_HWSTAMP
+ if (status & MACB_PTP_INT_MASK)
+ macb_ptp_int(queue, status);
+#endif
+
status = queue_readl(queue, ISR);
}
@@ -1592,8 +1662,10 @@ static int macb_start_xmit(struct sk_buff *skb, struct net_device *dev)
/* Make newly initialized descriptor visible to hardware */
wmb();
-
- skb_tx_timestamp(skb);
+#ifdef CONFIG_MACB_USE_HWSTAMP
+ if (!bp->ptp_hw_support)
+#endif
+ skb_tx_timestamp(skb);
macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TSTART));
@@ -1921,9 +1993,13 @@ static void macb_configure_dma(struct macb *bp)
dmacfg &= ~GEM_BIT(TXCOEN);
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
- if (bp->hw_dma_cap == HW_DMA_CAP_64B)
+ if (bp->hw_dma_cap & HW_DMA_CAP_64B)
dmacfg |= GEM_BIT(ADDR64);
#endif
+#ifdef CONFIG_MACB_USE_HWSTAMP
+ if (bp->hw_dma_cap & HW_DMA_CAP_PTP)
+ dmacfg |= GEM_BIT(RXEXT) | GEM_BIT(TXEXT);
+#endif
netdev_dbg(bp->dev, "Cadence configure DMA with 0x%08x\n",
dmacfg);
gem_writel(bp, DMACFG, dmacfg);
@@ -1971,21 +2047,22 @@ static void macb_init_hw(struct macb *bp)
/* Initialize TX and RX buffers */
macb_writel(bp, RBQP, lower_32_bits(bp->rx_ring_dma));
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
- if (bp->hw_dma_cap == HW_DMA_CAP_64B)
+ if (bp->hw_dma_cap & HW_DMA_CAP_64B)
macb_writel(bp, RBQPH, upper_32_bits(bp->rx_ring_dma));
#endif
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
queue_writel(queue, TBQP, lower_32_bits(queue->tx_ring_dma));
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
- if (bp->hw_dma_cap == HW_DMA_CAP_64B)
- queue_writel(queue, TBQPH, upper_32_bits(queue->tx_ring_dma));
+ if (bp->hw_dma_cap & HW_DMA_CAP_64B)
+ queue_writel(queue, TBQPH,
+ upper_32_bits(queue->tx_ring_dma));
#endif
/* Enable interrupts */
queue_writel(queue, IER,
- MACB_RX_INT_FLAGS |
- MACB_TX_INT_FLAGS |
- MACB_BIT(HRESP));
+ MACB_RX_INT_FLAGS |
+ MACB_TX_INT_FLAGS |
+ MACB_BIT(HRESP));
}
/* Enable TX and RX */
@@ -2446,6 +2523,71 @@ static int macb_set_ringparam(struct net_device *netdev,
return 0;
}
+#ifdef CONFIG_MACB_USE_HWSTAMP
+static unsigned int gem_get_tsu_rate(struct macb *bp)
+{
+ struct clk *tsu_clk;
+ unsigned int tsu_rate;
+
+ tsu_clk = devm_clk_get(&bp->pdev->dev, "tsu_clk");
+ if (!IS_ERR(tsu_clk))
+ tsu_rate = clk_get_rate(tsu_clk);
+ /* try pclk instead */
+ else if (!IS_ERR(bp->pclk)) {
+ tsu_clk = bp->pclk;
+ tsu_rate = clk_get_rate(tsu_clk);
+ } else
+ return -ENOTSUPP;
+ return tsu_rate;
+}
+
+static s32 gem_get_ptp_max_adj(void)
+{
+ return 64E6;
+}
+
+static int gem_get_ts_info(struct net_device *dev,
+ struct ethtool_ts_info *info)
+{
+ struct macb *bp = netdev_priv(dev);
+
+ ethtool_op_get_ts_info(dev, info);
+ if (!bp->ptp_hw_support)
+ return 0;
+
+ info->so_timestamping =
+ SOF_TIMESTAMPING_TX_SOFTWARE |
+ SOF_TIMESTAMPING_RX_SOFTWARE |
+ SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+ info->tx_types =
+ (1 << HWTSTAMP_TX_ONESTEP_SYNC) |
+ (1 << HWTSTAMP_TX_OFF) |
+ (1 << HWTSTAMP_TX_ON);
+ info->rx_filters =
+ (1 << HWTSTAMP_FILTER_NONE) |
+ (1 << HWTSTAMP_FILTER_ALL);
+ info->phc_index = -1;
+
+ if (bp->ptp_clock)
+ info->phc_index = ptp_clock_index(bp->ptp_clock);
+
+ return 0;
+}
+
+static struct macb_ptp_info gem_ptp_info = {
+ .ptp_init = gem_ptp_init,
+ .ptp_remove = gem_ptp_remove,
+ .get_ptp_max_adj = gem_get_ptp_max_adj,
+ .get_tsu_rate = gem_get_tsu_rate,
+ .get_ts_info = gem_get_ts_info,
+ .get_hwtst = gem_get_hwtst,
+ .set_hwtst = gem_set_hwtst,
+};
+#endif
+
static int macb_get_ts_info(struct net_device *netdev,
struct ethtool_ts_info *info)
{
@@ -2579,6 +2721,18 @@ static void macb_configure_caps(struct macb *bp,
dcfg = gem_readl(bp, DCFG2);
if ((dcfg & (GEM_BIT(RX_PKT_BUFF) | GEM_BIT(TX_PKT_BUFF))) == 0)
bp->caps |= MACB_CAPS_FIFO_MODE;
+ /* if HWSTAMP is configure and gem has the capability */
+#ifdef CONFIG_MACB_USE_HWSTAMP
+ bp->ptp_hw_support = false;
+ if (gem_has_ptp(bp)) {
+ if (!GEM_BFEXT(TSU, gem_readl(bp, DCFG5)))
+ pr_err("GEM doesn't support hardware ptp.\n");
+ else {
+ bp->ptp_hw_support = true;
+ bp->ptp_info = &gem_ptp_info;
+ }
+ }
+#endif
}
dev_dbg(&bp->pdev->dev, "Cadence caps 0x%08x\n", bp->caps);
@@ -2716,7 +2870,7 @@ static int macb_init(struct platform_device *pdev)
queue->IMR = GEM_IMR(hw_q - 1);
queue->TBQP = GEM_TBQP(hw_q - 1);
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
- if (bp->hw_dma_cap == HW_DMA_CAP_64B)
+ if (bp->hw_dma_cap & HW_DMA_CAP_64B)
queue->TBQPH = GEM_TBQPH(hw_q - 1);
#endif
} else {
@@ -2727,7 +2881,7 @@ static int macb_init(struct platform_device *pdev)
queue->IMR = MACB_IMR;
queue->TBQP = MACB_TBQP;
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
- if (bp->hw_dma_cap == HW_DMA_CAP_64B)
+ if (bp->hw_dma_cap & HW_DMA_CAP_64B)
queue->TBQPH = MACB_TBQPH;
#endif
}
@@ -3184,7 +3338,9 @@ static const struct macb_config np4_config = {
};
static const struct macb_config zynqmp_config = {
- .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO,
+ .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE |
+ MACB_CAPS_JUMBO |
+ MACB_CAPS_GEM_HAS_PTP,
.dma_burst_length = 16,
.clk_init = macb_clk_init,
.init = macb_init,
@@ -3218,7 +3374,9 @@ MODULE_DEVICE_TABLE(of, macb_dt_ids);
#endif /* CONFIG_OF */
static const struct macb_config default_gem_config = {
- .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO,
+ .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE |
+ MACB_CAPS_JUMBO |
+ MACB_CAPS_GEM_HAS_PTP,
.dma_burst_length = 16,
.clk_init = macb_clk_init,
.init = macb_init,
@@ -3307,19 +3465,24 @@ static int macb_probe(struct platform_device *pdev)
bp->wol |= MACB_WOL_HAS_MAGIC_PACKET;
device_init_wakeup(&pdev->dev, bp->wol & MACB_WOL_HAS_MAGIC_PACKET);
-#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
- if (GEM_BFEXT(DAW64, gem_readl(bp, DCFG6))) {
- dma_set_mask(&pdev->dev, DMA_BIT_MASK(44));
- bp->hw_dma_cap = HW_DMA_CAP_64B;
- } else
- bp->hw_dma_cap = HW_DMA_CAP_32B;
-#endif
-
spin_lock_init(&bp->lock);
/* setup capabilities */
macb_configure_caps(bp, macb_config);
+#ifdef MACB_EXT_DESC
+ bp->hw_dma_cap = HW_DMA_CAP_32B;
+#endif
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+ if (GEM_BFEXT(DAW64, gem_readl(bp, DCFG6))) {
+ dma_set_mask(&pdev->dev, DMA_BIT_MASK(44));
+ bp->hw_dma_cap |= HW_DMA_CAP_64B;
+ }
+#endif
+#ifdef CONFIG_MACB_USE_HWSTAMP
+ if (bp->ptp_hw_support)
+ bp->hw_dma_cap |= HW_DMA_CAP_PTP;
+#endif
platform_set_drvdata(pdev, dev);
dev->irq = platform_get_irq(pdev, 0);
diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h
index 234a49e..07a1e8e 100644
--- a/drivers/net/ethernet/cadence/macb.h
+++ b/drivers/net/ethernet/cadence/macb.h
@@ -11,6 +11,13 @@
#define _MACB_H
#include <linux/phy.h>
+#include <linux/ptp_clock.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/net_tstamp.h>
+
+#if defined(CONFIG_ARCH_DMA_ADDR_T_64BIT) || defined(CONFIG_MACB_USE_HWSTAMP)
+#define MACB_EXT_DESC
+#endif
#define MACB_GREGS_NBR 16
#define MACB_GREGS_VERSION 2
@@ -86,6 +93,10 @@
#define GEM_SA3T 0x009C /* Specific3 Top */
#define GEM_SA4B 0x00A0 /* Specific4 Bottom */
#define GEM_SA4T 0x00A4 /* Specific4 Top */
+#define GEM_EFTSH 0x00e8 /* PTP Event Frame Transmitted Seconds Register 47:32 */
+#define GEM_EFRSH 0x00ec /* PTP Event Frame Received Seconds Register 47:32 */
+#define GEM_PEFTSH 0x00f0 /* PTP Peer Event Frame Transmitted Seconds Register 47:32 */
+#define GEM_PEFRSH 0x00f4 /* PTP Peer Event Frame Received Seconds Register 47:32 */
#define GEM_OTX 0x0100 /* Octets transmitted */
#define GEM_OCTTXL 0x0100 /* Octets transmitted [31:0] */
#define GEM_OCTTXH 0x0104 /* Octets transmitted [47:32] */
@@ -155,6 +166,9 @@
#define GEM_DCFG6 0x0294 /* Design Config 6 */
#define GEM_DCFG7 0x0298 /* Design Config 7 */
+#define GEM_TXBDCTRL 0x04cc /* TX Buffer Descriptor control register */
+#define GEM_RXBDCTRL 0x04d0 /* RX Buffer Descriptor control register */
+
#define GEM_ISR(hw_q) (0x0400 + ((hw_q) << 2))
#define GEM_TBQP(hw_q) (0x0440 + ((hw_q) << 2))
#define GEM_TBQPH(hw_q) (0x04C8)
@@ -191,6 +205,8 @@
#define MACB_TZQ_OFFSET 12 /* Transmit zero quantum pause frame */
#define MACB_TZQ_SIZE 1
#define MACB_SRTSM_OFFSET 15
+#define MACB_OSSMODE_OFFSET 24 /* Enable One Step Synchro Mode */
+#define MACB_OSSMODE_SIZE 1
/* Bitfields in NCFGR */
#define MACB_SPD_OFFSET 0 /* Speed */
@@ -269,6 +285,10 @@
#define GEM_RXBS_SIZE 8
#define GEM_DDRP_OFFSET 24 /* disc_when_no_ahb */
#define GEM_DDRP_SIZE 1
+#define GEM_RXEXT_OFFSET 28 /* RX extended Buffer Descriptor mode */
+#define GEM_RXEXT_SIZE 1
+#define GEM_TXEXT_OFFSET 29 /* TX extended Buffer Descriptor mode */
+#define GEM_TXEXT_SIZE 1
#define GEM_ADDR64_OFFSET 30 /* Address bus width - 64b or 32b */
#define GEM_ADDR64_SIZE 1
@@ -354,6 +374,16 @@
#define MACB_PDRSFT_SIZE 1
#define MACB_SRI_OFFSET 26 /* TSU Seconds Register Increment */
#define MACB_SRI_SIZE 1
+#define MACB_TCI_OFFSET 29 /* TSU timer comparison interrupt */
+#define MACB_TCI_SIZE 1
+#define MACB_PTP_INT_MASK (MACB_BIT(DRQFR) \
+ | MACB_BIT(SFR) \
+ | MACB_BIT(DRQFT) \
+ | MACB_BIT(SFT) \
+ | MACB_BIT(PDRQFR) \
+ | MACB_BIT(PDRSFR) \
+ | MACB_BIT(PDRQFT) \
+ | MACB_BIT(PDRSFT))
/* Timer increment fields */
#define MACB_TI_CNS_OFFSET 0
@@ -425,6 +455,11 @@
#define GEM_TX_PKT_BUFF_OFFSET 21
#define GEM_TX_PKT_BUFF_SIZE 1
+
+/* Bitfields in DCFG5. */
+#define GEM_TSU_OFFSET 8
+#define GEM_TSU_SIZE 1
+
/* Bitfields in DCFG6. */
#define GEM_PBUF_LSO_OFFSET 27
#define GEM_PBUF_LSO_SIZE 1
@@ -439,6 +474,52 @@
#define GEM_NSINCR_OFFSET 0
#define GEM_NSINCR_SIZE 8
+/* Bitfields in TSH */
+#define GEM_TSH_OFFSET 0 /* TSU timer value (s). MSB [47:32] of seconds timer count */
+#define GEM_TSH_SIZE 16
+
+/* Bitfields in TSL */
+#define GEM_TSL_OFFSET 0 /* TSU timer value (s). LSB [31:0] of seconds timer count */
+#define GEM_TSL_SIZE 32
+
+/* Bitfields in TN */
+#define GEM_TN_OFFSET 0 /* TSU timer value (ns) */
+#define GEM_TN_SIZE 30
+
+/* Bitfields in TXBDCTRL */
+#define GEM_TXTSMODE_OFFSET 4 /* TX Descriptor Timestamp Insertion mode */
+#define GEM_TXTSMODE_SIZE 2
+
+/* Bitfields in RXBDCTRL */
+#define GEM_RXTSMODE_OFFSET 4 /* RX Descriptor Timestamp Insertion mode */
+#define GEM_RXTSMODE_SIZE 2
+
+/* Transmit DMA buffer descriptor Word 1 */
+#define GEM_DMA_TXVALID_OFFSET 23 /* timestamp has been captured in the Buffer Descriptor */
+#define GEM_DMA_TXVALID_SIZE 1
+
+/* Receive DMA buffer descriptor Word 0 */
+#define GEM_DMA_RXVALID_OFFSET 2 /* indicates a valid timestamp in the Buffer Descriptor */
+#define GEM_DMA_RXVALID_SIZE 1
+
+/* DMA buffer descriptor Word 2 (32 bit addressing) or Word 4 (64 bit addressing) */
+#define GEM_DMA_SECL_OFFSET 30 /* Timestamp seconds[1:0] */
+#define GEM_DMA_SECL_SIZE 2
+#define GEM_DMA_NSEC_OFFSET 0 /* Timestamp nanosecs [29:0] */
+#define GEM_DMA_NSEC_SIZE 30
+
+/* DMA buffer descriptor Word 3 (32 bit addressing) or Word 5 (64 bit addressing) */
+
+/* New hardware supports 12 bit precision of timestamp in DMA buffer descriptor.
+ * Old hardware supports only 6 bit precision but it is enough for PTP.
+ * Less accuracy is used always instead of checking hardware version.
+ */
+#define GEM_DMA_SECH_OFFSET 0 /* Timestamp seconds[5:2] */
+#define GEM_DMA_SECH_SIZE 4
+#define GEM_DMA_SEC_WIDTH (GEM_DMA_SECH_SIZE + GEM_DMA_SECL_SIZE)
+#define GEM_DMA_SEC_TOP (1 << GEM_DMA_SEC_WIDTH)
+#define GEM_DMA_SEC_MASK (GEM_DMA_SEC_TOP - 1)
+
/* Bitfields in ADJ */
#define GEM_ADDSUB_OFFSET 31
#define GEM_ADDSUB_SIZE 1
@@ -514,6 +595,8 @@
#define queue_readl(queue, reg) (queue)->bp->macb_reg_readl((queue)->bp, (queue)->reg)
#define queue_writel(queue, reg, value) (queue)->bp->macb_reg_writel((queue)->bp, (queue)->reg, (value))
+#define PTP_TS_BUFFER_SIZE 128 /* must be power of 2 */
+
/* Conditional GEM/MACB macros. These perform the operation to the correct
* register dependent on whether the device is a GEM or a MACB. For registers
* and bitfields that are common across both devices, use macb_{read,write}l
@@ -546,16 +629,21 @@ struct macb_dma_desc {
u32 ctrl;
};
-#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
-enum macb_hw_dma_cap {
- HW_DMA_CAP_32B,
- HW_DMA_CAP_64B,
-};
+#ifdef MACB_EXT_DESC
+#define HW_DMA_CAP_32B 0
+#define HW_DMA_CAP_64B (1 << 0)
+#define HW_DMA_CAP_PTP (1 << 1)
+#define HW_DMA_CAP_64B_PTP (HW_DMA_CAP_64B | HW_DMA_CAP_PTP)
struct macb_dma_desc_64 {
u32 addrh;
u32 resvd;
};
+
+struct macb_dma_desc_ptp {
+ u32 ts_1;
+ u32 ts_2;
+};
#endif
/* DMA descriptor bitfields */
@@ -871,6 +959,18 @@ struct macb_config {
int jumbo_max_len;
};
+#ifdef CONFIG_MACB_USE_HWSTAMP
+struct tsu_incr {
+ u32 sub_ns;
+ u32 ns;
+};
+
+struct gem_tx_ts {
+ struct sk_buff *skb;
+ struct macb_dma_desc_ptp desc_ptp;
+};
+#endif
+
struct macb_queue {
struct macb *bp;
int irq;
@@ -887,6 +987,12 @@ struct macb_queue {
struct macb_tx_skb *tx_skb;
dma_addr_t tx_ring_dma;
struct work_struct tx_error_task;
+
+#ifdef CONFIG_MACB_USE_HWSTAMP
+ struct work_struct tx_ts_task;
+ unsigned int tx_ts_head, tx_ts_tail;
+ struct gem_tx_ts tx_timestamps[PTP_TS_BUFFER_SIZE];
+#endif
};
struct macb {
@@ -955,11 +1061,67 @@ struct macb {
u32 wol;
struct macb_ptp_info *ptp_info; /* macb-ptp interface */
-#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
- enum macb_hw_dma_cap hw_dma_cap;
+#ifdef MACB_EXT_DESC
+ uint8_t hw_dma_cap;
+#endif
+
+#ifdef CONFIG_MACB_USE_HWSTAMP
+ bool ptp_hw_support;
+ spinlock_t tsu_clk_lock; /* gem tsu clock locking */
+ unsigned int tsu_rate;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info ptp_clock_info;
+ struct tsu_incr tsu_incr;
+ struct hwtstamp_config tstamp_config;
#endif
};
+#ifdef CONFIG_MACB_USE_HWSTAMP
+#define GEM_TSEC_SIZE (GEM_TSH_SIZE + GEM_TSL_SIZE)
+#define TSU_SEC_MAX_VAL (((u64)1 << GEM_TSEC_SIZE) - 1)
+#define TSU_NSEC_MAX_VAL ((1 << GEM_TN_SIZE) - 1)
+
+enum macb_bd_control {
+ TSTAMP_DISABLED,
+ TSTAMP_FRAME_PTP_EVENT_ONLY,
+ TSTAMP_ALL_PTP_FRAMES,
+ TSTAMP_ALL_FRAMES,
+};
+
+void gem_ptp_init(struct net_device *ndev);
+void gem_ptp_remove(struct net_device *ndev);
+int gem_ptp_txstamp(struct macb_queue *queue, struct sk_buff *skb, struct macb_dma_desc *des);
+void gem_ptp_rxstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc);
+void macb_ptp_int(struct macb_queue *queue, u32 status);
+static inline int gem_ptp_do_txstamp(struct macb_queue *queue, struct sk_buff *skb, struct macb_dma_desc *desc)
+{
+ if (queue->bp->tstamp_config.tx_type == TSTAMP_DISABLED)
+ return -ENOTSUPP;
+
+ return gem_ptp_txstamp(queue, skb, desc);
+}
+
+static inline void gem_ptp_do_rxstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc)
+{
+ if (bp->tstamp_config.rx_filter == TSTAMP_DISABLED)
+ return;
+
+ gem_ptp_rxstamp(bp, skb, desc);
+}
+int gem_get_hwtst(struct net_device *dev, struct ifreq *rq);
+int gem_set_hwtst(struct net_device *dev, struct ifreq *ifr, int cmd);
+#else
+static inline void gem_ptp_init(struct net_device *ndev) { }
+static inline void gem_ptp_remove(struct net_device *ndev) { }
+
+static inline int gem_ptp_do_txstamp(struct macb_queue *queue, struct sk_buff *skb, struct macb_dma_desc *desc)
+{
+ return 0;
+}
+
+static inline void gem_ptp_do_rxstamp(struct macb *bp, struct sk_buff *skb, struct macb_dma_desc *desc) { }
+#endif
+
static inline bool macb_is_gem(struct macb *bp)
{
return !!(bp->caps & MACB_CAPS_MACB_IS_GEM);
diff --git a/drivers/net/ethernet/cadence/macb_ptp.c b/drivers/net/ethernet/cadence/macb_ptp.c
new file mode 100755
index 0000000..72a79c4
--- /dev/null
+++ b/drivers/net/ethernet/cadence/macb_ptp.c
@@ -0,0 +1,724 @@
+/**
+ * 1588 PTP support for Cadence GEM device.
+ *
+ * Copyright (C) 2017 Cadence Design Systems - http://www.cadence.com
+ *
+ * Authors: Rafal Ozieblo <rafalo@cadence.com>
+ * Bartosz Folta <bfolta@cadence.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+#include <linux/platform_device.h>
+#include <linux/time64.h>
+#include <linux/ptp_classify.h>
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+#include <linux/net_tstamp.h>
+#include <linux/circ_buf.h>
+#include <linux/spinlock.h>
+
+#include "macb.h"
+
+#define GEM_PTP_TIMER_NAME "gem-ptp-timer"
+
+static struct macb_dma_desc_ptp *macb_ptp_desc(struct macb *bp,
+ struct macb_dma_desc *desc)
+{
+ if (bp->hw_dma_cap == HW_DMA_CAP_PTP)
+ return (struct macb_dma_desc_ptp *)
+ ((u8 *)desc + sizeof(struct macb_dma_desc));
+ if (bp->hw_dma_cap == HW_DMA_CAP_64B_PTP)
+ return (struct macb_dma_desc_ptp *)
+ ((u8 *)desc + sizeof(struct macb_dma_desc)
+ + sizeof(struct macb_dma_desc_64));
+ return NULL;
+}
+
+static int gem_tsu_get_time(struct macb *bp, struct timespec64 *ts)
+{
+ long first, second;
+ u32 secl, sech;
+ unsigned long flags;
+
+ if (!bp || !ts)
+ return -EINVAL;
+
+ spin_lock_irqsave(&bp->tsu_clk_lock, flags);
+ first = gem_readl(bp, TN);
+ secl = gem_readl(bp, TSL);
+ sech = gem_readl(bp, TSH);
+ second = gem_readl(bp, TN);
+
+ /* test for nsec rollover */
+ if (first > second) {
+ /* if so, use later read & re-read seconds
+ * (assume all done within 1s)
+ */
+ ts->tv_nsec = gem_readl(bp, TN);
+ secl = gem_readl(bp, TSL);
+ sech = gem_readl(bp, TSH);
+ } else
+ ts->tv_nsec = first;
+
+ ts->tv_sec = (((u64)sech << GEM_TSL_SIZE) | secl)
+ & TSU_SEC_MAX_VAL;
+
+ spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
+ return 0;
+}
+
+static int gem_tsu_set_time(struct macb *bp, const struct timespec64 *ts)
+{
+ u32 ns, sech, secl;
+ unsigned long flags;
+
+ if (!bp || !ts)
+ return -EINVAL;
+
+ secl = (u32)ts->tv_sec;
+ sech = (ts->tv_sec >> GEM_TSL_SIZE) & ((1 << GEM_TSH_SIZE) - 1);
+ ns = ts->tv_nsec;
+
+ spin_lock_irqsave(&bp->tsu_clk_lock, flags);
+
+ /* TSH doesn't latch the time and no atomicity! */
+ gem_writel(bp, TN, 0); /* clear to avoid overflow */
+ gem_writel(bp, TSH, sech);
+ /* write lower bits 2nd, for synchronized secs update */
+ gem_writel(bp, TSL, secl);
+ gem_writel(bp, TN, ns);
+
+ spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
+
+ return 0;
+}
+
+static int gem_tsu_incr_set(struct macb *bp, struct tsu_incr *incr_spec)
+{
+ unsigned long flags;
+
+ if (!bp || !incr_spec)
+ return -EINVAL;
+
+ /* tsu_timer_incr register must be written after
+ * the tsu_timer_incr_sub_ns register and the write operation
+ * will cause the value written to the tsu_timer_incr_sub_ns register
+ * to take effect.
+ */
+ spin_lock_irqsave(&bp->tsu_clk_lock, flags);
+ gem_writel(bp, TISUBN, GEM_BF(SUBNSINCR, incr_spec->sub_ns));
+ gem_writel(bp, TI, GEM_BF(NSINCR, incr_spec->ns));
+ spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
+
+ return 0;
+}
+
+static int gem_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct tsu_incr incr_spec;
+ struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
+ u64 adj;
+ u32 word;
+ bool neg_adj = false;
+
+ if (!ptp)
+ return -EINVAL;
+
+ if (scaled_ppm < 0) {
+ neg_adj = true;
+ scaled_ppm = -scaled_ppm;
+ }
+
+ /* Adjustment is relative to base frequency */
+ incr_spec.sub_ns = bp->tsu_incr.sub_ns;
+ incr_spec.ns = bp->tsu_incr.ns;
+
+ /* scaling: unused(8bit) | ns(8bit) | fractions(16bit) */
+ word = ((u64)incr_spec.ns << GEM_SUBNSINCR_SIZE) + incr_spec.sub_ns;
+ adj = (u64)scaled_ppm * word;
+ /* Divide with rounding, equivalent to floating dividing:
+ * (temp / USEC_PER_SEC) + 0.5
+ */
+ adj += (USEC_PER_SEC >> 1);
+ adj >>= GEM_SUBNSINCR_SIZE; /* remove fractions */
+ adj = div_u64(adj, USEC_PER_SEC);
+ adj = neg_adj ? (word - adj) : (word + adj);
+
+ incr_spec.ns = (adj >> GEM_SUBNSINCR_SIZE)
+ & ((1 << GEM_NSINCR_SIZE) - 1);
+ incr_spec.sub_ns = adj & ((1 << GEM_SUBNSINCR_SIZE) - 1);
+ gem_tsu_incr_set(bp, &incr_spec);
+ return 0;
+}
+
+static int gem_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct timespec64 now, then = ns_to_timespec64(delta);
+ struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
+ u32 adj, sign = 0;
+
+ if (!ptp)
+ return -EINVAL;
+
+ if (delta < 0) {
+ sign = 1;
+ delta = -delta;
+ }
+
+ if (delta > TSU_NSEC_MAX_VAL) {
+ gem_tsu_get_time(bp, &now);
+ if (sign)
+ now = timespec64_sub(now, then);
+ else
+ now = timespec64_add(now, then);
+
+ gem_tsu_set_time(bp, (const struct timespec64 *)&now);
+ } else {
+ adj = (sign << GEM_ADDSUB_OFFSET) | delta;
+
+ gem_writel(bp, TA, adj);
+ }
+
+ return 0;
+}
+
+static int gem_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
+{
+ struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
+
+ if (!ptp || !ts)
+ return -EINVAL;
+
+ gem_tsu_get_time(bp, ts);
+ return 0;
+}
+
+static int gem_ptp_settime(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
+
+ if (!ptp || !ts)
+ return -EINVAL;
+
+ gem_tsu_set_time(bp, ts);
+ return 0;
+}
+
+static int gem_ptp_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
+
+ if (!ptp || !rq)
+ return -EINVAL;
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_EXTTS: /* Toggle TSU match interrupt */
+ if (on)
+ macb_writel(bp, IER, MACB_BIT(TCI));
+ else
+ macb_writel(bp, IDR, MACB_BIT(TCI));
+ break;
+ case PTP_CLK_REQ_PEROUT: /* Toggle Periodic output */
+ return -EOPNOTSUPP;
+ /* break; */
+ case PTP_CLK_REQ_PPS: /* Toggle TSU periodic (second) interrupt */
+ if (on)
+ macb_writel(bp, IER, MACB_BIT(SRI));
+ else
+ macb_writel(bp, IDR, MACB_BIT(SRI));
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static struct ptp_clock_info gem_ptp_caps_template = {
+ .owner = THIS_MODULE,
+ .name = GEM_PTP_TIMER_NAME,
+ .max_adj = 0,
+ .n_alarm = 1,
+ .n_ext_ts = 1,
+ .n_per_out = 0,
+ .n_pins = 0,
+ .pps = 1,
+ .adjfine = gem_ptp_adjfine,
+ .adjtime = gem_ptp_adjtime,
+ .gettime64 = gem_ptp_gettime,
+ .settime64 = gem_ptp_settime,
+ .enable = gem_ptp_enable,
+};
+
+static void gem_ptp_init_timer(struct macb *bp)
+{
+ u32 rem = 0;
+
+ bp->tsu_incr.ns = div_u64_rem(NSEC_PER_SEC, bp->tsu_rate, &rem);
+ if (rem) {
+ u64 adj = rem;
+
+ adj <<= GEM_SUBNSINCR_SIZE;
+ bp->tsu_incr.sub_ns = div_u64(adj, bp->tsu_rate);
+ } else {
+ bp->tsu_incr.sub_ns = 0;
+ }
+}
+
+static void gem_ptp_init_tsu(struct macb *bp)
+{
+ struct timespec64 ts;
+
+ /* 1. get current system time */
+ ts = ns_to_timespec64(ktime_to_ns(ktime_get_real()));
+
+ /* 2. set ptp timer */
+ gem_tsu_set_time(bp, &ts);
+
+ /* 3. set PTP timer increment value to BASE_INCREMENT */
+ gem_tsu_incr_set(bp, &bp->tsu_incr);
+
+ gem_writel(bp, TA, 0);
+}
+
+static void gem_ptp_clear_timer(struct macb *bp)
+{
+ bp->tsu_incr.ns = 0;
+ bp->tsu_incr.sub_ns = 0;
+
+ gem_writel(bp, TISUBN, GEM_BF(SUBNSINCR, 0));
+ gem_writel(bp, TI, GEM_BF(NSINCR, 0));
+ gem_writel(bp, TA, 0);
+}
+
+static int gem_hw_timestamp(struct macb *bp,
+ u32 dma_desc_ts_1, u32 dma_desc_ts_2, struct timespec64 *ts)
+{
+ struct timespec64 tsu;
+
+ ts->tv_sec = (GEM_BFEXT(DMA_SECH, dma_desc_ts_2) << GEM_DMA_SECL_SIZE) |
+ GEM_BFEXT(DMA_SECL, dma_desc_ts_1);
+ ts->tv_nsec = GEM_BFEXT(DMA_NSEC, dma_desc_ts_1);
+
+ /* TSU overlaping workaround
+ * The timestamp only contains lower few bits of seconds,
+ * so add value from 1588 timer
+ */
+ gem_tsu_get_time(bp, &tsu);
+
+ /* If the top bit is set in the timestamp,
+ * but not in 1588 timer, it has rolled over,
+ * so subtract max size
+ */
+ if ((ts->tv_sec & (GEM_DMA_SEC_TOP >> 1)) &&
+ !(tsu.tv_sec & (GEM_DMA_SEC_TOP >> 1)))
+ ts->tv_sec -= GEM_DMA_SEC_TOP;
+
+ ts->tv_sec += ((~GEM_DMA_SEC_MASK) & (tsu.tv_sec));
+
+ return 0;
+}
+
+void gem_ptp_rxstamp(struct macb *bp, struct sk_buff *skb,
+ struct macb_dma_desc *desc)
+{
+ struct timespec64 ts;
+ struct skb_shared_hwtstamps *shhwtstamps = skb_hwtstamps(skb);
+ struct macb_dma_desc_ptp *desc_ptp;
+
+ if (GEM_BFEXT(DMA_RXVALID, desc->addr)) {
+ desc_ptp = macb_ptp_desc(bp, desc);
+ gem_hw_timestamp(bp, desc_ptp->ts_1, desc_ptp->ts_2, &ts);
+ memset(shhwtstamps, 0, sizeof(struct skb_shared_hwtstamps));
+ shhwtstamps->hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
+ }
+}
+
+static void gem_tstamp_tx(struct macb *bp, struct sk_buff *skb,
+ struct macb_dma_desc_ptp *desc_ptp)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct timespec64 ts;
+
+ gem_hw_timestamp(bp, desc_ptp->ts_1, desc_ptp->ts_2, &ts);
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
+ skb_tstamp_tx(skb, &shhwtstamps);
+}
+
+int gem_ptp_txstamp(struct macb_queue *queue, struct sk_buff *skb,
+ struct macb_dma_desc *desc)
+{
+ struct gem_tx_ts *tx_timestamp;
+ struct macb_dma_desc_ptp *desc_ptp;
+ unsigned long head = queue->tx_ts_head;
+ unsigned long tail = READ_ONCE(queue->tx_ts_tail);
+
+ if (!GEM_BFEXT(DMA_TXVALID, desc->ctrl))
+ return -EINVAL;
+
+ if (CIRC_SPACE(head, tail, PTP_TS_BUFFER_SIZE) == 0)
+ return -ENOMEM;
+
+ desc_ptp = macb_ptp_desc(queue->bp, desc);
+ tx_timestamp = &queue->tx_timestamps[head];
+ tx_timestamp->skb = skb;
+ tx_timestamp->desc_ptp.ts_1 = desc_ptp->ts_1;
+ tx_timestamp->desc_ptp.ts_2 = desc_ptp->ts_2;
+ /* move head */
+ smp_store_release(&queue->tx_ts_head,
+ (head + 1) & (PTP_TS_BUFFER_SIZE - 1));
+ return 0;
+}
+
+static void gem_tx_timestamp_flush(struct work_struct *work)
+{
+ struct macb_queue *queue =
+ container_of(work, struct macb_queue, tx_ts_task);
+ struct gem_tx_ts *tx_ts;
+ unsigned long head, tail;
+
+ /* take current head */
+ head = smp_load_acquire(&queue->tx_ts_head);
+ tail = queue->tx_ts_tail;
+
+ while (CIRC_CNT(head, tail, PTP_TS_BUFFER_SIZE)) {
+ tx_ts = &queue->tx_timestamps[tail];
+ gem_tstamp_tx(queue->bp, tx_ts->skb, &tx_ts->desc_ptp);
+ /* cleanup */
+ dev_kfree_skb_any(tx_ts->skb);
+ /* remove old tail */
+ smp_store_release(&queue->tx_ts_tail,
+ (tail + 1) & (PTP_TS_BUFFER_SIZE - 1));
+ tail = queue->tx_ts_tail;
+ }
+}
+
+void gem_ptp_init(struct net_device *dev)
+{
+ struct macb *bp = netdev_priv(dev);
+ unsigned int q;
+ struct macb_queue *queue;
+
+ bp->ptp_clock_info = gem_ptp_caps_template;
+
+ /* nominal frequency and maximum adjustment in ppb */
+ bp->tsu_rate = bp->ptp_info->get_tsu_rate(bp);
+ bp->ptp_clock_info.max_adj = bp->ptp_info->get_ptp_max_adj();
+ gem_ptp_init_timer(bp);
+ bp->ptp_clock = ptp_clock_register(&bp->ptp_clock_info, &dev->dev);
+ if (IS_ERR(&bp->ptp_clock)) {
+ bp->ptp_clock = NULL;
+ pr_err("ptp clock register failed\n");
+ return;
+ }
+
+ spin_lock_init(&bp->tsu_clk_lock);
+ for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ queue->tx_ts_head = 0;
+ queue->tx_ts_tail = 0;
+ INIT_WORK(&queue->tx_ts_task, gem_tx_timestamp_flush);
+ queue_writel(queue, IER, MACB_PTP_INT_MASK);
+ }
+
+ gem_ptp_init_tsu(bp);
+
+ dev_info(&bp->pdev->dev, "%s ptp clock registered.\n",
+ GEM_PTP_TIMER_NAME);
+}
+
+void gem_ptp_remove(struct net_device *ndev)
+{
+ struct macb *bp = netdev_priv(ndev);
+
+ if (bp->ptp_clock)
+ ptp_clock_unregister(bp->ptp_clock);
+
+ gem_ptp_clear_timer(bp);
+
+ dev_info(&bp->pdev->dev, "%s ptp clock unregistered.\n",
+ GEM_PTP_TIMER_NAME);
+}
+
+static int gem_ptp_set_ts_mode(struct macb *bp,
+ enum macb_bd_control tx_bd_control,
+ enum macb_bd_control rx_bd_control)
+{
+ if (!bp)
+ return -EINVAL;
+
+ gem_writel(bp, TXBDCTRL, GEM_BF(TXTSMODE, tx_bd_control));
+ gem_writel(bp, RXBDCTRL, GEM_BF(RXTSMODE, rx_bd_control));
+
+ return 0;
+}
+
+int gem_get_hwtst(struct net_device *dev, struct ifreq *rq)
+{
+ struct macb *bp = netdev_priv(dev);
+ struct hwtstamp_config *tstamp_config = &bp->tstamp_config;
+
+ if (!bp->ptp_hw_support)
+ return -EFAULT;
+
+ if (copy_to_user(rq->ifr_data, tstamp_config, sizeof(*tstamp_config)))
+ return -EFAULT;
+ else
+ return 0;
+}
+
+static int gem_ptp_set_one_step_sync(struct macb *bp, u8 enable)
+{
+ u32 reg_val;
+
+ if (!bp || enable > 1)
+ return -EINVAL;
+
+ reg_val = macb_readl(bp, NCR);
+
+ if (enable)
+ macb_writel(bp, NCR, reg_val | MACB_BIT(OSSMODE));
+ else
+ macb_writel(bp, NCR, reg_val & ~MACB_BIT(OSSMODE));
+
+ return 0;
+}
+
+int gem_set_hwtst(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct macb *bp = netdev_priv(dev);
+ struct hwtstamp_config *tstamp_config = &bp->tstamp_config;
+ enum macb_bd_control tx_bd_control = TSTAMP_DISABLED;
+ enum macb_bd_control rx_bd_control = TSTAMP_DISABLED;
+ u32 regval;
+
+ if (!bp->ptp_hw_support)
+ return -EFAULT;
+
+ if (copy_from_user(tstamp_config, ifr->ifr_data,
+ sizeof(*tstamp_config)))
+ return -EFAULT;
+
+ /* reserved for future extensions */
+ if (tstamp_config->flags)
+ return -EINVAL;
+
+ switch (tstamp_config->tx_type) {
+ case HWTSTAMP_TX_OFF:
+ break;
+ case HWTSTAMP_TX_ONESTEP_SYNC:
+ if (gem_ptp_set_one_step_sync(bp, 1) != 0)
+ return -ERANGE;
+ case HWTSTAMP_TX_ON:
+ tx_bd_control = TSTAMP_ALL_FRAMES;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (tstamp_config->rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ rx_bd_control = TSTAMP_ALL_PTP_FRAMES;
+ tstamp_config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+ regval = macb_readl(bp, NCR);
+ macb_writel(bp, NCR, (regval | MACB_BIT(SRTSM)));
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ case HWTSTAMP_FILTER_ALL:
+ rx_bd_control = TSTAMP_ALL_FRAMES;
+ tstamp_config->rx_filter = HWTSTAMP_FILTER_ALL;
+ break;
+ default:
+ tstamp_config->rx_filter = HWTSTAMP_FILTER_NONE;
+ return -ERANGE;
+ }
+
+ if (gem_ptp_set_ts_mode(bp, tx_bd_control, rx_bd_control) != 0)
+ return -ERANGE;
+
+ if (copy_to_user(ifr->ifr_data, tstamp_config, sizeof(*tstamp_config)))
+ return -EFAULT;
+ else
+ return 0;
+}
+
+static int gem_ptp_time_peer_frame_tx_get(struct macb *bp,
+ struct timespec64 *ts)
+{
+ if (!bp || !ts)
+ return -EINVAL;
+
+ ts->tv_sec = (((u64)gem_readl(bp, PEFTSH) << 32) |
+ gem_readl(bp, PEFTSL)) & TSU_SEC_MAX_VAL;
+ ts->tv_nsec = gem_readl(bp, PEFTN);
+
+ return 0;
+}
+
+static int gem_ptp_time_peer_frame_rx_get(struct macb *bp,
+ struct timespec64 *ts)
+{
+ if (!bp || !ts)
+ return -EINVAL;
+
+ ts->tv_sec = (((u64)gem_readl(bp, PEFRSH) << 32) |
+ gem_readl(bp, PEFRSL)) & TSU_SEC_MAX_VAL;
+ ts->tv_nsec = gem_readl(bp, PEFRN);
+
+ return 0;
+}
+
+static int gem_ptp_time_frame_tx_get(struct macb *bp, struct timespec64 *ts)
+{
+ if (!bp || !ts)
+ return -EINVAL;
+
+ ts->tv_sec = (((u64)gem_readl(bp, EFTSH) << 32) |
+ gem_readl(bp, EFTSL)) & TSU_SEC_MAX_VAL;
+ ts->tv_nsec = gem_readl(bp, EFTN);
+
+ return 0;
+}
+
+static int gem_ptp_time_frame_rx_get(struct macb *bp, struct timespec64 *ts)
+{
+ if (!bp || !ts)
+ return -EINVAL;
+
+ ts->tv_sec = (((u64)gem_readl(bp, EFRSH) << 32) |
+ gem_readl(bp, EFRSL)) & TSU_SEC_MAX_VAL;
+ ts->tv_nsec = gem_readl(bp, EFRN);
+
+ return 0;
+}
+
+static int gem_ptp_event(struct macb *bp, struct timespec64 *ts)
+{
+ struct ptp_clock_event event;
+
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec;
+
+ ptp_clock_event(bp->ptp_clock, &event);
+
+ return 0;
+}
+
+void macb_ptp_int(struct macb_queue *queue, u32 status)
+{
+ struct macb *bp = queue->bp;
+ struct timespec64 ts;
+
+ if (status & MACB_BIT(DRQFR)) {
+ if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
+ queue_writel(queue, ISR, MACB_BIT(DRQFR));
+ if (gem_ptp_time_frame_rx_get(bp, &ts) != 0) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ gem_ptp_event(bp, &ts);
+ }
+
+ if (status & MACB_BIT(SFR)) {
+ if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
+ queue_writel(queue, ISR, MACB_BIT(SFR));
+ if (gem_ptp_time_frame_rx_get(bp, &ts) != 0) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ gem_ptp_event(bp, &ts);
+ }
+
+ if (status & MACB_BIT(DRQFT)) {
+ if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
+ queue_writel(queue, ISR, MACB_BIT(DRQFT));
+ if (gem_ptp_time_frame_tx_get(bp, &ts) != 0) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ gem_ptp_event(bp, &ts);
+ }
+
+ if (status & MACB_BIT(SFT)) {
+ if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
+ queue_writel(queue, ISR, MACB_BIT(SFT));
+ if (gem_ptp_time_frame_tx_get(bp, &ts) != 0) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ gem_ptp_event(bp, &ts);
+ }
+
+ if (status & MACB_BIT(PDRQFR)) {
+ if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
+ queue_writel(queue, ISR, MACB_BIT(PDRQFR));
+ if (gem_ptp_time_peer_frame_rx_get(bp, &ts) != 0) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ gem_ptp_event(bp, &ts);
+ }
+
+ if (status & MACB_BIT(PDRSFR)) {
+ if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
+ queue_writel(queue, ISR, MACB_BIT(PDRSFR));
+ if (gem_ptp_time_peer_frame_rx_get(bp, &ts) != 0) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ gem_ptp_event(bp, &ts);
+ }
+
+ if (status & MACB_BIT(PDRQFT)) {
+ if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
+ queue_writel(queue, ISR,
+ MACB_BIT(PDRQFT));
+ if (gem_ptp_time_peer_frame_tx_get(bp, &ts) != 0) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ gem_ptp_event(bp, &ts);
+ }
+
+ if (status & MACB_BIT(PDRSFT)) {
+ if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
+ queue_writel(queue, ISR,
+ MACB_BIT(PDRSFT));
+ if (gem_ptp_time_peer_frame_tx_get(bp, &ts) != 0) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ gem_ptp_event(bp, &ts);
+ }
+}
--
2.4.5
^ permalink raw reply related
* [PATCH] i2c: add sc18is600 driver
From: Sebastian Reichel @ 2017-03-29 14:03 UTC (permalink / raw)
To: Sebastian Reichel, Wolfram Sang
Cc: Rob Herring, Mark Rutland, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
This adds an I²C master driver for SPI -> I²C bus bridge chips.
It currently supports NXP's SC18IS600 and SC18IS601, as well as
Silicon Labs' CP2120. The driver was only tested on SC18IS600.
Signed-off-By: Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
.../devicetree/bindings/i2c/i2c-cp2120.txt | 1 +
.../devicetree/bindings/i2c/i2c-sc18is600.txt | 62 +++
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-sc18is600.c | 572 +++++++++++++++++++++
5 files changed, 646 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-cp2120.txt
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sc18is600.txt
create mode 100644 drivers/i2c/busses/i2c-sc18is600.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-cp2120.txt b/Documentation/devicetree/bindings/i2c/i2c-cp2120.txt
new file mode 100644
index 000000000000..95e06e74f288
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-cp2120.txt
@@ -0,0 +1 @@
+Please see binding for i2c-sc18is600
diff --git a/Documentation/devicetree/bindings/i2c/i2c-sc18is600.txt b/Documentation/devicetree/bindings/i2c/i2c-sc18is600.txt
new file mode 100644
index 000000000000..d0d9e680a5d6
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-sc18is600.txt
@@ -0,0 +1,62 @@
+NXP SC18IS600 and Silabs CP2120 - SPI to I2C bus bridge
+
+NXP SC18IS600 is a SPI slave chip, which implements an I²C host,
+also known as SPI to I²C bus bridge. SC18IS601 is the same chip,
+but has an external clock input instead of using a builtin
+oscillator. CP2120 is a similar chip from Silabs, which implements
+the same interface as NXP's SC18IS600.
+
+Required properties:
+ - compatible: Should contain one of
+ * "nxp,sc18is600"
+ * "nxp,sc18is601"
+ * "silabs,cp2120"
+ - reg: address of the chip on SPI bus
+ - interrupts: Interrupt specifier. Refer to interrupt bindings.
+ - #address-cells: Should be 1.
+ - #size-cells: Should be 0.
+
+Required properties for sc18is601:
+ - clkin: Clock specifier for CLKIN pin
+
+Optional properties:
+ - clock-frequency:
+ Desired I2C bus frequency in Hz, otherwise defaults to 100 KHz
+ - reset-gpios
+ GPIO specifier for reset pin, which is active low.
+ - vdd-supply
+ Regulator specifier for VDD supply (3.3V).
+ - Child nodes conforming to i2c bus binding
+
+Example:
+
+&spi_controller {
+ sc18is600: i2c@0 {
+ compatible = "nxp,sc18is600";
+ spi-max-frequency = <700000>; /* 700KHz */
+ spi-cpol;
+ spi-cpha;
+ reg = <0>;
+
+ vdd-supply = <®ulator_v33>;
+
+ interrupt-parent = <&socgpio>;
+ interrupts = <25 0x2>;
+
+ reset-gpios = <&i2cgpio1 9 GPIO_ACTIVE_LOW>;
+
+ clock-frequency = <100000>; /* 100KHz */
+
+ #address-cells = <0x1>;
+ #size-cells = <0x0>;
+ };
+};
+
+&sc18is600 {
+ i2c_device@42 {
+ compatible = "some,i2c-device";
+ reg = <0x42>;
+ };
+
+ ...
+};
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 8adc0f1d7ad0..3e6386ff8de3 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -210,6 +210,16 @@ config I2C_NFORCE2_S4985
This driver can also be built as a module. If so, the module
will be called i2c-nforce2-s4985.
+config I2C_SC18IS600
+ tristate "NXP SC18IS600"
+ depends on SPI && REGMAP
+ help
+ If you say yes to this option, support will be included for the
+ NXP SC18IS600 SPI to I2C-bus interface.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-sc18is600.
+
config I2C_SIS5595
tristate "SiS 5595"
depends on PCI
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 30b60855fbcd..29971aebd238 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_I2C_ISMT) += i2c-ismt.o
obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o
obj-$(CONFIG_I2C_NFORCE2_S4985) += i2c-nforce2-s4985.o
obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o
+obj-$(CONFIG_I2C_SC18IS600) += i2c-sc18is600.o
obj-$(CONFIG_I2C_SIS5595) += i2c-sis5595.o
obj-$(CONFIG_I2C_SIS630) += i2c-sis630.o
obj-$(CONFIG_I2C_SIS96X) += i2c-sis96x.o
diff --git a/drivers/i2c/busses/i2c-sc18is600.c b/drivers/i2c/busses/i2c-sc18is600.c
new file mode 100644
index 000000000000..e4d4b3caf3a9
--- /dev/null
+++ b/drivers/i2c/busses/i2c-sc18is600.c
@@ -0,0 +1,572 @@
+/*
+ * NXP SC18IS600 SPI to I2C bus interface driver
+ *
+ * Copyright (C) 2017 Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Datasheets:
+ * - http://www.nxp.com/documents/data_sheet/SC18IS600.pdf
+ * - https://www.silabs.com/documents/public/data-sheets/CP2120.pdf
+ */
+
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#define SC18IS600_I2C_PM_TIMEOUT 1000 /* ms */
+#define SC18IS600_DEFAULT_FREQ 100000
+
+#define SC18IS600_CMD_WR 0x00 /* write */
+#define SC18IS600_CMD_RD 0x01 /* read */
+#define SC18IS600_CMD_WR_RD 0x02 /* read after write */
+#define SC18IS600_CMD_WR_WR 0x03 /* write after write */
+#define SC18IS600_CMD_RDBUF 0x06 /* read buffer */
+#define CP2120_CMD_WRMULTI 0x09 /* write to multiple slaves */
+#define SC18IS600_CMD_SPICON 0x18 /* spi endianess configuration */
+#define SC18IS600_CMD_REG_WR 0x20 /* write register */
+#define SC18IS600_CMD_REG_RD 0x21 /* read register */
+#define SC18IS600_CMD_PWRDWN 0x30 /* power down */
+#define CP2120_CMD_REVISION 0x40 /* read revision */
+
+#define SC18IS600_REG_IO_CONFIG 0x00
+#define SC18IS600_REG_IO_STATE 0x01
+#define SC18IS600_REG_I2C_CLOCK 0x02
+#define SC18IS600_REG_I2C_TIMEOUT 0x03
+#define SC18IS600_REG_I2C_STAT 0x04
+#define SC18IS600_REG_I2C_ADDR 0x05
+#define SC18IS600_REG_I2C_BUFFER 0x06 /* only cp2120 */
+#define SC18IS600_REG_IO_CONFIG2 0x07 /* only cp2120 */
+#define SC18IS600_REG_EDGEINT 0x08 /* only cp2120 */
+#define SC18IS600_REG_I2C_TIMEOUT2 0x09 /* only cp2120 */
+
+#define SC18IS600_STAT_OK 0xF0
+#define SC18IS600_STAT_NAK_ADDR 0xF1
+#define SC18IS600_STAT_NAK_DATA 0xF2
+#define SC18IS600_STAT_BUSY 0xF3
+#define SC18IS600_STAT_TIMEOUT 0xF8
+#define SC18IS600_STAT_SIZE 0xF9
+#define SC18IS600_STAT_TIMEOUT2 0xFA /* only cp2120 */
+#define SC18IS600_STAT_BLOCKED 0xFB /* only cp2120 */
+
+#define CMD_BUFFER_SIZE 5
+
+enum chiptype {
+ SPI2I2C_SC18IS600,
+ SPI2I2C_SC18IS601,
+ SPI2I2C_CP2120,
+};
+
+struct chipdesc {
+ u8 type;
+ u32 max_spi_speed;
+ u32 buffer_size;
+ u32 clock_base;
+ u32 timeout_base;
+ const struct regmap_config *regmap_cfg;
+};
+
+static bool sc18is600_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SC18IS600_REG_I2C_STAT:
+ case SC18IS600_REG_I2C_BUFFER:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config sc18is600_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = 0x05,
+ .writeable_reg = sc18is600_writeable_reg,
+};
+
+static const struct regmap_config cp2120_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = 0x09,
+ .writeable_reg = sc18is600_writeable_reg,
+};
+
+/*
+ * Note: The sc18is600's datasheet promises 1.2MHz SPI support, but my chip did
+ * not behave correctly at that speed. It received the bytes correctly, but
+ * just sent them back instead of interpreting them correctly. At 800 KHz I
+ * still got a few errors (about 1%) and at 700 KHz everything works smoothly.
+ */
+static const struct chipdesc chip_sc18is600 = {
+ .type = SPI2I2C_SC18IS600,
+ .max_spi_speed = 700000,
+ .buffer_size = 96,
+ .clock_base = 1843200,
+ .buffer_size = 96,
+ .clock_base = 1843200,
+ .timeout_base = 1125, /* 112.5 Hz */
+ .regmap_cfg = &sc18is600_regmap_config,
+};
+
+static const struct chipdesc chip_sc18is601 = {
+ .type = SPI2I2C_SC18IS601,
+ .max_spi_speed = 3000000,
+ .buffer_size = 96,
+ .clock_base = 0,
+ .timeout_base = 1125, /* 112.5 Hz */
+ .regmap_cfg = &sc18is600_regmap_config,
+};
+
+static const struct chipdesc chip_cp2120 = {
+ .type = SPI2I2C_CP2120,
+ .max_spi_speed = 1000000,
+ .buffer_size = 255,
+ .clock_base = 2000000,
+ .timeout_base = 1280, /* 128 Hz */
+ .regmap_cfg = &cp2120_regmap_config,
+};
+
+struct sc18is600dev {
+ struct i2c_adapter adapter;
+ struct completion completion;
+ struct spi_device *spi;
+ struct regmap *regmap;
+ const struct chipdesc *chip;
+ struct gpio_desc *reset;
+ struct regulator *vdd;
+ struct clk *clk;
+ u32 clock_base;
+ u32 i2c_clock_frequency;
+ int state;
+};
+
+static irqreturn_t sc18is600_irq_handler(int this_irq, void *data)
+{
+ struct sc18is600dev *dev = data;
+ int err;
+
+ err = regmap_read(dev->regmap, SC18IS600_REG_I2C_STAT, &dev->state);
+ if (err)
+ return IRQ_NONE;
+
+ dev_vdbg(&dev->spi->dev, "irq received, stat=%08x", dev->state);
+
+ /* no irq is generated for busy state, so ignore this irq */
+ if (dev->state == SC18IS600_STAT_BUSY)
+ return IRQ_NONE;
+
+ complete(&dev->completion);
+ return IRQ_HANDLED;
+}
+
+static int reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ u8 txbuffer[2] = { SC18IS600_CMD_REG_RD, reg & 0xff };
+ u8 rxbuffer[1];
+ int err;
+
+ err = spi_write_then_read(spi, txbuffer, sizeof(txbuffer),
+ rxbuffer, sizeof(rxbuffer));
+ if (err)
+ return err;
+
+ *val = rxbuffer[0];
+
+ return 0;
+}
+
+static int reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ u8 txbuffer[3] = { SC18IS600_CMD_REG_WR, reg & 0xff, val & 0xff };
+
+ return spi_write(spi, txbuffer, sizeof(txbuffer));
+}
+
+static struct regmap_bus regmap_sc18is600_bus = {
+ .reg_write = reg_write,
+ .reg_read = reg_read,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
+};
+
+static void sc18is600_setup_clock_frequency(struct sc18is600dev *dev)
+{
+ int reg = DIV_ROUND_UP(dev->clock_base, dev->i2c_clock_frequency);
+
+ if (reg < 5)
+ reg = 5;
+ if (reg > 255)
+ reg = 255;
+
+ dev_dbg(&dev->spi->dev, "i2c clock frequency: %08x", reg);
+ regmap_write(dev->regmap, SC18IS600_REG_I2C_CLOCK, reg);
+}
+
+static void sc18is600_setup_timeout(struct sc18is600dev *dev,
+ bool enable, int timeout_ms)
+{
+ int timeout = DIV_ROUND_UP(timeout_ms * dev->chip->timeout_base, 10000);
+ u8 reg;
+
+ if (timeout <= 0)
+ timeout = 1;
+ if (timeout > 255)
+ timeout = 255;
+
+ reg = (timeout & 0x7F) << 1;
+ reg |= (!!enable);
+
+ dev_dbg(&dev->spi->dev, "i2c timeout: %08x", reg);
+ regmap_write(dev->regmap, SC18IS600_REG_I2C_TIMEOUT, reg);
+}
+
+static void sc18is600_reset(struct sc18is600dev *dev)
+{
+ if (dev->reset) {
+ gpiod_set_value_cansleep(dev->reset, 1);
+ usleep_range(50, 100);
+ gpiod_set_value_cansleep(dev->reset, 0);
+ usleep_range(50, 100);
+ }
+
+ sc18is600_setup_clock_frequency(dev);
+ sc18is600_setup_timeout(dev, true, 500);
+}
+
+static int sc18is600_read(struct sc18is600dev *dev, struct i2c_msg *msg)
+{
+ u8 header[] = { SC18IS600_CMD_RD, msg->len, msg->addr << 1 };
+ struct spi_transfer xfer[1] = { 0 };
+
+ xfer[0].tx_buf = header;
+ xfer[0].len = sizeof(header);
+
+ dev_dbg(&dev->spi->dev, "r(addr=%x, len=%d)", msg->addr, msg->len);
+ return spi_sync_transfer(dev->spi, xfer, 1);
+}
+
+static int sc18is600_write(struct sc18is600dev *dev, struct i2c_msg *msg)
+{
+ u8 header[] = { SC18IS600_CMD_WR, msg->len, msg->addr << 1 };
+ struct spi_transfer xfer[2] = { 0 };
+
+ xfer[0].tx_buf = header;
+ xfer[0].len = sizeof(header);
+
+ xfer[1].tx_buf = msg->buf;
+ xfer[1].len = msg->len;
+
+ dev_dbg(&dev->spi->dev, "w(addr=%x, len=%d)", msg->addr, msg->len);
+ return spi_sync_transfer(dev->spi, xfer, 2);
+}
+
+static int sc18is600_read_after_write(struct sc18is600dev *dev,
+ struct i2c_msg *msg1,
+ struct i2c_msg *msg2)
+{
+ u8 header1[] =
+ { SC18IS600_CMD_WR_RD, msg1->len, msg2->len, msg1->addr << 1 };
+ u8 header2[] = { msg2->addr << 1 };
+ struct spi_transfer xfer[3] = { 0 };
+
+ xfer[0].tx_buf = header1;
+ xfer[0].len = sizeof(header1);
+
+ xfer[1].tx_buf = msg1->buf;
+ xfer[1].len = msg1->len;
+
+ xfer[2].tx_buf = header2;
+ xfer[2].len = sizeof(header2);
+
+ dev_dbg(&dev->spi->dev, "w(addr=%x, len=%d) + r(addr=%x, len=%d)",
+ msg1->addr, msg1->len, msg2->addr, msg2->len);
+ return spi_sync_transfer(dev->spi, xfer, 3);
+}
+
+static int sc18is600_write_after_write(struct sc18is600dev *dev,
+ struct i2c_msg *msg1,
+ struct i2c_msg *msg2)
+{
+ u8 header1[] =
+ { SC18IS600_CMD_WR_WR, msg1->len, msg2->len, msg1->addr << 1 };
+ u8 header2[] = { msg2->addr << 1 };
+ struct spi_transfer xfer[4] = { 0 };
+
+ xfer[0].tx_buf = header1;
+ xfer[0].len = sizeof(header1);
+
+ xfer[1].tx_buf = msg1->buf;
+ xfer[1].len = msg1->len;
+
+ xfer[2].tx_buf = header2;
+ xfer[2].len = sizeof(header2);
+
+ xfer[3].tx_buf = msg2->buf;
+ xfer[3].len = msg2->len;
+
+ dev_dbg(&dev->spi->dev, "w(addr=%x, len=%d) + w(addr=%x, len=%d)",
+ msg1->addr, msg1->len, msg2->addr, msg2->len);
+ return spi_sync_transfer(dev->spi, xfer, 4);
+}
+
+static int sc18is600_read_buffer(struct sc18is600dev *dev, struct i2c_msg *msg)
+{
+ static const u8 read_buffer_cmd = SC18IS600_REG_I2C_BUFFER;
+
+ return spi_write_then_read(dev->spi, &read_buffer_cmd, 1,
+ msg->buf, msg->len);
+}
+
+static int sc18is600_xfer(struct i2c_adapter *adapter,
+ struct i2c_msg *msgs, int num)
+{
+ struct sc18is600dev *dev = adapter->algo_data;
+ int read_operations = 0;
+ int i, err;
+
+ for (i = 0; i < num; i++) {
+ if (msgs[i].len > dev->chip->buffer_size)
+ return -EOPNOTSUPP;
+
+ /* chip only support standard read & write */
+ if (msgs[i].flags & ~I2C_M_RD)
+ return -EOPNOTSUPP;
+
+ if (msgs[i].flags & I2C_M_RD)
+ read_operations++;
+ }
+
+ reinit_completion(&dev->completion);
+
+ if (num == 1 && read_operations == 1)
+ err = sc18is600_read(dev, &msgs[0]);
+ else if (num == 1)
+ err = sc18is600_write(dev, &msgs[0]);
+ else if (num == 2 && read_operations == 1)
+ err = sc18is600_read_after_write(dev, &msgs[0], &msgs[1]);
+ else if (num == 2)
+ err = sc18is600_write_after_write(dev, &msgs[0], &msgs[1]);
+ else
+ return -EOPNOTSUPP;
+
+ if (err) {
+ dev_err(&dev->spi->dev, "spi transfer failed: %d", err);
+ return err;
+ }
+
+ err = wait_for_completion_timeout(&dev->completion, adapter->timeout);
+ if (!err) {
+ dev_warn(&dev->spi->dev,
+ "timeout waiting for irq, poll status register");
+ dev->state = SC18IS600_STAT_BUSY;
+ regmap_read(dev->regmap, SC18IS600_REG_I2C_STAT, &dev->state);
+ }
+
+ switch (dev->state) {
+ case SC18IS600_STAT_OK:
+ break;
+ case SC18IS600_STAT_NAK_ADDR:
+ return -EIO;
+ case SC18IS600_STAT_NAK_DATA:
+ return -EREMOTEIO;
+ case SC18IS600_STAT_SIZE:
+ return -EINVAL;
+ case SC18IS600_STAT_TIMEOUT:
+ return -ETIMEDOUT;
+ case SC18IS600_STAT_TIMEOUT2:
+ return -ETIMEDOUT;
+ case SC18IS600_STAT_BLOCKED:
+ return -ETIMEDOUT;
+ default:
+ case SC18IS600_STAT_BUSY:
+ dev_err(&dev->spi->dev, "device hangup detected, reset!");
+ sc18is600_reset(dev);
+ return -EAGAIN;
+ }
+
+ if (!read_operations)
+ return 0;
+
+ err = sc18is600_read_buffer(dev, &msgs[num-1]);
+ if (err)
+ return err;
+
+ return num;
+}
+
+static u32 sc18is600_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm sc18is600_algorithm = {
+ .master_xfer = sc18is600_xfer,
+ .functionality = sc18is600_func,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id sc18is600_of_match[] = {
+ { .compatible = "nxp,sc18is600", .data = &chip_sc18is600 },
+ { .compatible = "nxp,sc18is601", .data = &chip_sc18is601 },
+ { .compatible = "silabs,cp2120", .data = &chip_cp2120 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sc18is600_of_match);
+#endif
+
+static int sc18is600_probe(struct spi_device *spi)
+{
+ const struct of_device_id *of_id;
+ struct sc18is600dev *dev;
+ int err;
+
+ of_id = of_match_device(sc18is600_of_match, &spi->dev);
+ if (!of_id)
+ return -ENODEV;
+
+ dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);
+ if (dev == NULL)
+ return -ENOMEM;
+ spi_set_drvdata(spi, dev);
+
+ init_completion(&dev->completion);
+
+ dev->spi = spi;
+ dev->adapter.owner = THIS_MODULE;
+ dev->adapter.class = I2C_CLASS_DEPRECATED;
+ dev->adapter.algo = &sc18is600_algorithm;
+ dev->adapter.algo_data = dev;
+ dev->adapter.dev.parent = &spi->dev;
+ dev->chip = of_id->data;
+
+ snprintf(dev->adapter.name, sizeof(dev->adapter.name),
+ "SC18IS600 at SPI %02d device %02d",
+ spi->master->bus_num, spi->chip_select);
+
+ spi->bits_per_word = 8;
+ spi->mode = SPI_MODE_3;
+ spi->max_speed_hz = dev->chip->max_spi_speed;
+
+ err = spi_setup(spi);
+ if (err)
+ return err;
+
+ dev->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(dev->reset)) {
+ err = PTR_ERR(dev->reset);
+ dev_err(&spi->dev, "Failed to reset gpio, err: %d\n", err);
+ return err;
+ }
+
+ err = devm_request_threaded_irq(&spi->dev, spi->irq, NULL,
+ sc18is600_irq_handler,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "sc18is600", dev);
+ if (err) {
+ dev_err(&spi->dev, "Failed to request irq, err: %d\n", err);
+ return err;
+ }
+
+ dev->regmap = devm_regmap_init(&dev->spi->dev,
+ ®map_sc18is600_bus, &dev->spi->dev,
+ dev->chip->regmap_cfg);
+ if (IS_ERR(dev->regmap)) {
+ err = PTR_ERR(dev->regmap);
+ dev_err(&spi->dev, "Failed to init regmap, err: %d\n", err);
+ return err;
+ }
+
+ err = device_property_read_u32(&spi->dev, "clock-frequency",
+ &dev->i2c_clock_frequency);
+ if (err) {
+ dev->i2c_clock_frequency = SC18IS600_DEFAULT_FREQ;
+ dev_dbg(&spi->dev, "using default frequency %u\n",
+ dev->i2c_clock_frequency);
+ }
+
+ dev->vdd = devm_regulator_get(&spi->dev, "vdd");
+ if (IS_ERR(dev->vdd)) {
+ err = PTR_ERR(dev->vdd);
+ dev_err(&spi->dev, "could not acquire vdd: %d\n", err);
+ return err;
+ }
+
+ if (!dev->chip->clock_base) {
+ dev->clk = devm_clk_get(&spi->dev, "clkin");
+ if (IS_ERR(dev->clk)) {
+ err = PTR_ERR(dev->clk);
+ dev_err(&spi->dev, "could not acquire vdd: %d\n", err);
+ return err;
+ }
+
+ clk_prepare_enable(dev->clk);
+
+ dev->clock_base = clk_get_rate(dev->clk) / 4;
+ } else {
+ dev->clock_base = dev->chip->clock_base;
+ }
+
+ err = regulator_enable(dev->vdd);
+ if (err) {
+ dev_err(&spi->dev, "could not enable vdd: %d\n", err);
+ return err;
+ }
+
+ sc18is600_reset(dev);
+
+ err = i2c_add_adapter(&dev->adapter);
+ if (err)
+ goto out_disable_regulator;
+
+ return 0;
+
+out_disable_regulator:
+ regulator_disable(dev->vdd);
+ return err;
+}
+
+static int sc18is600_remove(struct spi_device *spi)
+{
+ struct sc18is600dev *dev = spi_get_drvdata(spi);
+
+ i2c_del_adapter(&dev->adapter);
+
+ regulator_disable(dev->vdd);
+
+ return 0;
+}
+
+static struct spi_driver sc18is600_driver = {
+ .probe = sc18is600_probe,
+ .remove = sc18is600_remove,
+ .driver = {
+ .name = "i2c-sc18is600",
+ .of_match_table = of_match_ptr(sc18is600_of_match),
+ },
+};
+module_spi_driver(sc18is600_driver);
+
+MODULE_AUTHOR("Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>");
+MODULE_DESCRIPTION("NXP SC18IS600 I2C bus adapter");
+MODULE_LICENSE("GPL");
--
2.11.0
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* RE: [PATCH v2 2/7] dt-bindings: pinctrl: Add RZ/A1 bindings doc
From: Chris Brandt @ 2017-03-29 14:09 UTC (permalink / raw)
To: Linus Walleij
Cc: jacopo, Geert Uytterhoeven, Jacopo Mondi, Geert Uytterhoeven,
Laurent Pinchart, Rob Herring, Mark Rutland, Russell King,
Linux-Renesas, linux-gpio@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <CACRpkdb+k82OUbi1y1SNC09asHsNSqbtvCkYyT=1eqjpdMt_cg@mail.gmail.com>
On Wednesday, March 29, 2017, Linus Walleij:
> If you prefer to use preprocessor macros or whatever to make the bitmasks
> or how you want to organize the constants in your include files is not my
> concern, do whatever you seem fit, just pack it into a 32bit thing somehow
> which makes sense from a maintenance point of view.
OK, I think everyone agrees that a single 32-bit value is fine because macros
will also for good readability and maintenance.
> >> Not only because it will save use from having a loong list(*) of
> >> macros that has to be kept up to date when/if new RZ hardware will
> >> arrive, but also because of readability and simplicity for down-stream
> and BSP users.
> >> Speaking of which, I would like to know what does Chris think of this.
> >
> > The list of macros would be very long, especially against the
> > different packaging version of the RZ/A1 series. 11 ports, 16-pins for
> > each port, 8 different function options for each pin....2 different
> package/pin variations.
> >
> > And at the end of the day....there is no benefit for the user over
> > just using a macro.
>
> I don't know who has this idea that you could not use macros, certainly
> not me. Some misunderstanding must be going on. For what I'm concerned you
> can write hex numbers in the pinmux = <0x12345678>;
>
> > The reason for the "FLAGS" is to work around a quirky hardware design
> (in my opinion).
>
> The flags I don't like at all, and think they should be converted to
> generic pin config because they have nothing to do with muxing.
>
> But I will point that out in the specific patch adding them.
OK, I think I understand your issue a little better of mixing user-defined config
with generic pinmux.
From our perspective, the FLAGS (BI_DIR, SWIO_IN, and SWIO_OUT ) were not really
optional when selecting what you want the pin to do....so we considered it part
of the pin-mux.
In the hardware manual, there are tables that say that for 'some' certain
pins/functions "just setting the pin to a function is not enough...you need to make
another register setting (that may or may not make sense), otherwise it's not going
to work".
So, trying to get the driver to be that smart across all the different pin/package
variations seems to be way too ugly (and a maintenance nightmare). Simply putting
that in the DT binding was much more cleaner.
As for how to pass this HW info into the driver, I'll move over to the other email
thread where you started to give some suggestions...
Chris
^ permalink raw reply
* RE: [PATCH v2 2/7] rtc: Add rtc-sh
From: Chris Brandt @ 2017-03-29 14:14 UTC (permalink / raw)
To: Geert Uytterhoeven, Rob Herring
Cc: Alessandro Zummo, Alexandre Belloni, Mark Rutland, Simon Horman,
RTCLINUX, devicetree@vger.kernel.org, Linux-Renesas
In-Reply-To: <CAMuHMdVmwJYMzspVeDqToBrkfK+zGLMNhxGvEbqCA-vuCUXqoQ@mail.gmail.com>
On Wednesday, March 29, 2017, Geert Uytterhoeven wrote:
> >> +Example:
> >> +rtc: rtc@fcff1000 {
> >> + compatible = "renesas,r7s72100-rtc", "renesas,sh-rtc";
> >> + reg = <0xfcff1000 0x2e>;
> >> + interrupts = <GIC_SPI 276 IRQ_TYPE_EDGE_RISING
> >> + GIC_SPI 277 IRQ_TYPE_EDGE_RISING
> >> + GIC_SPI 278 IRQ_TYPE_EDGE_RISING>;
> >> + interrupt-names = "alarm", "period", "carry";
> >> + clocks = <&mstp6_clks R7S72100_CLK_RTC>, <&rtc_x1_clk>,
> >> + <&rtc_x3_clk>, <&extal_clk>;
> >> + clock-names = "fck", "rtc_x1", "rtc_x3", "extal";
> >> + power-domains = <&cpg_clocks>;
> >
> > Not documented.
>
> "power-domains" is a platform property.
>
> All hardware components need power.
> All synchronous hardware components need a clock.
> Most hardware components have a reset signal.
>
> Whether these are exposed and can be controlled depends on the
> platform/SoC.
> So documenting them in each and every device binding looks overkill to me.
> I think this is something to be addressed by devicetree-specification
> (which doesn't handle clocks, power-domains, resets yet).
>
> If you prefer, the property can be removed from the example, though.
I'll go ahead and pull it out (since I have to update the commit title
anyway as per Rob's request)
Besides...it's probably the most least important part of the example ;)
Thanks,
Chris
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox