Devicetree
 help / color / mirror / Atom feed
* [PATCH v3 4/9] i2c: at91: add new platform support for sam9x60
From: Eugen.Hristev @ 2019-07-09 13:19 UTC (permalink / raw)
  To: wsa, peda, mark.rutland, Ludovic.Desroches, linux-i2c, devicetree,
	linux-arm-kernel, linux-kernel, pierre-yves.mordret,
	alexandre.belloni, robh+dt
  Cc: Nicolas.Ferre, Eugen.Hristev
In-Reply-To: <1562678049-17581-1-git-send-email-eugen.hristev@microchip.com>

From: Eugen Hristev <eugen.hristev@microchip.com>

Add new platform data support for the sam9x60 SoC

Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>
---
 drivers/i2c/busses/i2c-at91-core.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/i2c/busses/i2c-at91-core.c b/drivers/i2c/busses/i2c-at91-core.c
index 8d55cdd..a663a7a 100644
--- a/drivers/i2c/busses/i2c-at91-core.c
+++ b/drivers/i2c/busses/i2c-at91-core.c
@@ -148,6 +148,14 @@ static struct at91_twi_pdata sama5d2_config = {
 	.has_hold_field = true,
 };
 
+static struct at91_twi_pdata sam9x60_config = {
+	.clk_max_div = 7,
+	.clk_offset = 4,
+	.has_unre_flag = true,
+	.has_alt_cmd = true,
+	.has_hold_field = true,
+};
+
 static const struct of_device_id atmel_twi_dt_ids[] = {
 	{
 		.compatible = "atmel,at91rm9200-i2c",
@@ -174,6 +182,9 @@ static const struct of_device_id atmel_twi_dt_ids[] = {
 		.compatible = "atmel,sama5d2-i2c",
 		.data = &sama5d2_config,
 	}, {
+		.compatible = "microchip,sam9x60-i2c",
+		.data = &sam9x60_config,
+	}, {
 		/* sentinel */
 	}
 };
-- 
2.7.4

^ permalink raw reply related

* [PATCH v3 3/9] i2c: add support for filter-width-ns optional property
From: Eugen.Hristev @ 2019-07-09 13:19 UTC (permalink / raw)
  To: wsa, peda, mark.rutland, Ludovic.Desroches, linux-i2c, devicetree,
	linux-arm-kernel, linux-kernel, pierre-yves.mordret,
	alexandre.belloni, robh+dt
  Cc: Nicolas.Ferre, Eugen.Hristev
In-Reply-To: <1562678049-17581-1-git-send-email-eugen.hristev@microchip.com>

From: Eugen Hristev <eugen.hristev@microchip.com>

This optional timing property specifies the width of the spikes on the i2c
lines (in ns) that can be filtered out by built-in analog or digital filters
which are embedded in some i2c controllers.
Include it in the timings structure and read it as integer property.

Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>
---
 drivers/i2c/i2c-core-base.c | 2 ++
 include/linux/i2c.h         | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
index 9e43508..73d1c62 100644
--- a/drivers/i2c/i2c-core-base.c
+++ b/drivers/i2c/i2c-core-base.c
@@ -1658,6 +1658,8 @@ void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t, bool use_de
 		t->sda_fall_ns = t->scl_fall_ns;
 
 	device_property_read_u32(dev, "i2c-sda-hold-time-ns", &t->sda_hold_ns);
+
+	device_property_read_u32(dev, "i2c-filter-width-ns", &t->filter_width_ns);
 }
 EXPORT_SYMBOL_GPL(i2c_parse_fw_timings);
 
diff --git a/include/linux/i2c.h b/include/linux/i2c.h
index 1308126..dfb6525 100644
--- a/include/linux/i2c.h
+++ b/include/linux/i2c.h
@@ -563,6 +563,7 @@ struct i2c_lock_operations {
  * @scl_int_delay_ns: time IP core additionally needs to setup SCL in ns
  * @sda_fall_ns: time SDA signal takes to fall in ns; t(f) in the I2C specification
  * @sda_hold_ns: time IP core additionally needs to hold SDA in ns
+ * @filter_width_ns: width in ns of spikes on i2c lines that the IP core can filter out
  */
 struct i2c_timings {
 	u32 bus_freq_hz;
@@ -571,6 +572,7 @@ struct i2c_timings {
 	u32 scl_int_delay_ns;
 	u32 sda_fall_ns;
 	u32 sda_hold_ns;
+	u32 filter_width_ns;
 };
 
 /**
-- 
2.7.4

^ permalink raw reply related

* [PATCH v3 2/9] dt-bindings: i2c: add bindings for i2c analog and digital filter
From: Eugen.Hristev @ 2019-07-09 13:19 UTC (permalink / raw)
  To: wsa, peda, mark.rutland, Ludovic.Desroches, linux-i2c, devicetree,
	linux-arm-kernel, linux-kernel, pierre-yves.mordret,
	alexandre.belloni, robh+dt
  Cc: Nicolas.Ferre, Eugen.Hristev
In-Reply-To: <1562678049-17581-1-git-send-email-eugen.hristev@microchip.com>

From: Eugen Hristev <eugen.hristev@microchip.com>

Some i2c controllers have a built-in digital or analog filter.
This is specifically required depending on the hardware PCB/board.
Some controllers also allow specifying the maximum width of the
spikes that can be filtered. The width length can be specified in nanoseconds.

Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>
---
 Documentation/devicetree/bindings/i2c/i2c.txt | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/Documentation/devicetree/bindings/i2c/i2c.txt b/Documentation/devicetree/bindings/i2c/i2c.txt
index 44efafd..a2d31aa 100644
--- a/Documentation/devicetree/bindings/i2c/i2c.txt
+++ b/Documentation/devicetree/bindings/i2c/i2c.txt
@@ -55,6 +55,17 @@ wants to support one of the below features, it should adapt the bindings below.
 	Number of nanoseconds the SDA signal takes to fall; t(f) in the I2C
 	specification.
 
+- i2c-ana-filter
+	Enable analog filter for i2c lines.
+
+- i2c-dig-filter
+	Enable digital filter for i2c lines.
+
+- i2c-filter-width-ns
+	Width of spikes which can be filtered by either digital or analog
+	filters (i2c-ana-filtr or i2c-dig-filtr). This width is specified
+	in nanoseconds.
+
 - interrupts
 	interrupts used by the device.
 
-- 
2.7.4

^ permalink raw reply related

* [PATCH v3 1/9] dt-bindings: i2c: at91: add new compatible
From: Eugen.Hristev @ 2019-07-09 13:19 UTC (permalink / raw)
  To: wsa, peda, mark.rutland, Ludovic.Desroches, linux-i2c, devicetree,
	linux-arm-kernel, linux-kernel, pierre-yves.mordret,
	alexandre.belloni, robh+dt
  Cc: Nicolas.Ferre, Eugen.Hristev
In-Reply-To: <1562678049-17581-1-git-send-email-eugen.hristev@microchip.com>

From: Eugen Hristev <eugen.hristev@microchip.com>

Add compatible for new Microchip SoC, sam9x60

Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>
---
 Documentation/devicetree/bindings/i2c/i2c-at91.txt | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/i2c/i2c-at91.txt b/Documentation/devicetree/bindings/i2c/i2c-at91.txt
index b7cec17..2210f43 100644
--- a/Documentation/devicetree/bindings/i2c/i2c-at91.txt
+++ b/Documentation/devicetree/bindings/i2c/i2c-at91.txt
@@ -3,7 +3,8 @@ I2C for Atmel platforms
 Required properties :
 - compatible : Must be "atmel,at91rm9200-i2c", "atmel,at91sam9261-i2c",
      "atmel,at91sam9260-i2c", "atmel,at91sam9g20-i2c", "atmel,at91sam9g10-i2c",
-     "atmel,at91sam9x5-i2c", "atmel,sama5d4-i2c" or "atmel,sama5d2-i2c"
+     "atmel,at91sam9x5-i2c", "atmel,sama5d4-i2c", "atmel,sama5d2-i2c" or
+     "microchip,sam9x60-i2c"
 - reg: physical base address of the controller and length of memory mapped
      region.
 - interrupts: interrupt number to the cpu.
-- 
2.7.4

^ permalink raw reply related

* [PATCH v3 0/9] i2c: add support for filters
From: Eugen.Hristev @ 2019-07-09 13:19 UTC (permalink / raw)
  To: wsa, peda, mark.rutland, Ludovic.Desroches, linux-i2c, devicetree,
	linux-arm-kernel, linux-kernel, pierre-yves.mordret,
	alexandre.belloni, robh+dt
  Cc: Nicolas.Ferre, Eugen.Hristev

From: Eugen Hristev <eugen.hristev@microchip.com>

Hello,

This series adds support for analog and digital filters for i2c controllers

This series is based on the series:
[PATCH v2 0/9] i2c: at91: filters support for at91 SoCs
and enhanced to add the bindings for all controllers plus an extra binding
for the width of the spikes in nanoseconds.

First, bindings are created for
'i2c-ana-filter'
'i2c-dig-filter'
'i2c-filter-width-ns'

The support is added in the i2c core to retrieve filter width and add it
to the timings structure.
Next, the at91 driver is enhanced for supporting digital filter, advanced
digital filter (with selectable spike width) and the analog filter.

Finally the device tree for two boards are modified to make use of the
new properties.

This series is the result of the comments on the ML in the direction
requested: to make the bindings globally available for i2c drivers.

Changes in v3:
- made bindings global for i2c controllers and modified accordingly
- gave up PADFCDF bit because it's a lack in datasheet
- the computation on the width of the spike is based on periph clock as it
is done for hold time.

Changes in v2:
- added device tree bindings and support for enable-ana-filt and
enable-dig-filt
- added the new properties to the DT for sama5d4_xplained/sama5d2_xplained

Eugen Hristev (9):
  dt-bindings: i2c: at91: add new compatible
  dt-bindings: i2c: add bindings for i2c analog and digital filter
  i2c: add support for filter-width-ns optional property
  i2c: at91: add new platform support for sam9x60
  i2c: at91: add support for digital filtering
  i2c: at91: add support for advanced digital filtering
  i2c: at91: add support for analog filtering
  ARM: dts: at91: sama5d2_xplained: add analog and digital filter for
    i2c
  ARM: dts: at91: sama5d4_xplained: add analog filter for i2c

 Documentation/devicetree/bindings/i2c/i2c-at91.txt |  3 +-
 Documentation/devicetree/bindings/i2c/i2c.txt      | 11 +++++
 arch/arm/boot/dts/at91-sama5d2_xplained.dts        |  6 +++
 arch/arm/boot/dts/at91-sama5d4_xplained.dts        |  1 +
 drivers/i2c/busses/i2c-at91-core.c                 | 38 +++++++++++++++++
 drivers/i2c/busses/i2c-at91-master.c               | 49 ++++++++++++++++++++--
 drivers/i2c/busses/i2c-at91.h                      | 13 ++++++
 drivers/i2c/i2c-core-base.c                        |  2 +
 include/linux/i2c.h                                |  2 +
 9 files changed, 121 insertions(+), 4 deletions(-)

-- 
2.7.4

^ permalink raw reply

* [PATCH v3 2/2] i3c: master: Add driver for MediaTek IP
From: Qii Wang @ 2019-07-09 13:09 UTC (permalink / raw)
  To: bbrezillon
  Cc: matthias.bgg, robh+dt, mark.rutland, linux-i3c, devicetree,
	linux-arm-kernel, linux-kernel, linux-mediatek, srv_heupstream,
	leilk.liu, qii.wang, liguo.zhang, xinping.qian
In-Reply-To: <1562677762-24067-1-git-send-email-qii.wang@mediatek.com>

Add a driver for MediaTek I3C master IP.

Signed-off-by: Qii Wang <qii.wang@mediatek.com>
---
 drivers/i3c/master/Kconfig          |   10 +
 drivers/i3c/master/Makefile         |    1 +
 drivers/i3c/master/i3c-master-mtk.c | 1239 +++++++++++++++++++++++++++++++++++
 3 files changed, 1250 insertions(+)
 create mode 100644 drivers/i3c/master/i3c-master-mtk.c

diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
index 26c6b58..acc00d9 100644
--- a/drivers/i3c/master/Kconfig
+++ b/drivers/i3c/master/Kconfig
@@ -20,3 +20,13 @@ config DW_I3C_MASTER
 
 	  This driver can also be built as a module.  If so, the module
 	  will be called dw-i3c-master.
+
+config MTK_I3C_MASTER
+	tristate "MediaTek I3C master driver"
+	depends on I3C
+	depends on HAS_IOMEM
+	depends on !(ALPHA || PARISC)
+	help
+	  This selects the MediaTek(R) I3C master controller driver.
+	  If you want to use MediaTek(R) I3C interface, say Y here.
+	  If unsure, say N or M.
diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
index fc53939..fe7ccf5 100644
--- a/drivers/i3c/master/Makefile
+++ b/drivers/i3c/master/Makefile
@@ -1,2 +1,3 @@
 obj-$(CONFIG_CDNS_I3C_MASTER)		+= i3c-master-cdns.o
 obj-$(CONFIG_DW_I3C_MASTER)		+= dw-i3c-master.o
+obj-$(CONFIG_MTK_I3C_MASTER)		+= i3c-master-mtk.o
diff --git a/drivers/i3c/master/i3c-master-mtk.c b/drivers/i3c/master/i3c-master-mtk.c
new file mode 100644
index 0000000..3981149
--- /dev/null
+++ b/drivers/i3c/master/i3c-master-mtk.c
@@ -0,0 +1,1239 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 MediaTek Design Systems Inc.
+ *
+ * Author: Qii Wang <qii.wang@mediatek.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/i3c/master.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define DRV_NAME		"i3c-master-mtk"
+
+#define SLAVE_ADDR		0x04
+#define INTR_MASK		0x08
+#define INTR_STAT		0x0c
+#define INTR_TRANSAC_COMP	BIT(0)
+#define INTR_ACKERR		GENMASK(2, 1)
+#define INTR_ARB_LOST		BIT(3)
+#define INTR_RS_MULTI		BIT(4)
+#define INTR_MAS_ERR		BIT(8)
+#define INTR_ALL		(INTR_MAS_ERR | INTR_ARB_LOST |\
+				INTR_ACKERR | INTR_TRANSAC_COMP)
+
+#define CONTROL			0x10
+#define CONTROL_WRAPPER		BIT(0)
+#define CONTROL_RS		BIT(1)
+#define CONTROL_DMA_EN		BIT(2)
+#define CONTROL_CLK_EXT_EN	BIT(3)
+#define CONTROL_DIR_CHANGE	BIT(4)
+#define CONTROL_ACKERR_DET_EN	BIT(5)
+#define CONTROL_LEN_CHANGE	BIT(6)
+#define CONTROL_DMAACK_EN	BIT(8)
+#define CONTROL_ASYNC_MODE	BIT(9)
+
+#define TRANSFER_LEN		0x14
+#define TRANSAC_LEN		0x18
+#define TRANSAC_LEN_WRRD	0x0002
+#define TRANS_ONE_LEN		0x0001
+
+#define DELAY_LEN		0x1c
+#define DELAY_LEN_DEFAULT	0x000a
+
+#define TIMING			0x20
+#define TIMING_VALUE(sample_cnt, step_cnt) ({ \
+	typeof(sample_cnt) sample_cnt_ = (sample_cnt); \
+	typeof(step_cnt) step_cnt_ = (step_cnt); \
+	(((sample_cnt_) << 8) | (step_cnt_)); \
+})
+
+#define START			0x24
+#define START_EN		BIT(0)
+#define START_MUL_TRIG		BIT(14)
+#define START_MUL_CNFG		BIT(15)
+
+#define EXT_CONF		0x28
+#define EXT_CONF_DEFAULT	0x0a1f
+
+#define LTIMING			0x2c
+#define LTIMING_VALUE(sample_cnt, step_cnt) ({ \
+	typeof(sample_cnt) sample_cnt_ = (sample_cnt); \
+	typeof(step_cnt) step_cnt_ = (step_cnt); \
+	(((sample_cnt_) << 6) | (step_cnt_) | \
+	((sample_cnt_) << 12) | ((step_cnt_) << 9)); \
+})
+
+#define HS			0x30
+#define HS_CLR_VALUE		0x0000
+#define HS_DEFAULT_VALUE	0x0083
+#define HS_VALUE(sample_cnt, step_cnt) ({ \
+	typeof(sample_cnt) sample_cnt_ = (sample_cnt); \
+	typeof(step_cnt) step_cnt_ = (step_cnt); \
+	(HS_DEFAULT_VALUE | \
+	((sample_cnt_) << 12) | ((step_cnt_) << 8)); \
+})
+
+#define IO_CONFIG		0x34
+#define IO_CONFIG_PUSH_PULL	0x0000
+
+#define FIFO_ADDR_CLR		0x38
+#define FIFO_CLR		0x0003
+
+#define MCU_INTR		0x40
+#define MCU_INTR_EN		BIT(0)
+
+#define TRANSFER_LEN_AUX	0x44
+#define CLOCK_DIV		0x48
+#define CLOCK_DIV_DEFAULT	((INTER_CLK_DIV - 1) << 8 |\
+				(INTER_CLK_DIV - 1))
+
+#define SOFTRESET		0x50
+#define SOFT_RST		BIT(0)
+
+#define TRAFFIC			0x54
+#define TRAFFIC_DAA_EN		BIT(4)
+#define TRAFFIC_TBIT		BIT(7)
+#define TRAFFIC_HEAD_ONLY	BIT(9)
+#define TRAFFIC_SKIP_SLV_ADDR	BIT(10)
+#define TRAFFIC_HANDOFF		BIT(14)
+
+#define DEF_DA			0x68
+#define DEF_DAA_SLV_PARITY	BIT(8)
+
+#define SHAPE			0x6c
+#define SHAPE_T_STALL		BIT(1)
+#define SHAPE_T_PARITY		BIT(2)
+
+#define HFIFO_DATA		0x70
+#define NINTH_BIT_IGNORE	0
+#define NINTH_BIT_ACK		1
+#define NINTH_BIT_NACK		2
+#define NINTH_BIT_ODD_PAR	3
+#define INST_WITH_HS		BIT(10)
+#define UNLOCK_HFIFO		BIT(15)
+#define HFIFO_DATA_08		0x8208
+#define HFIFO_DATA_7E		(UNLOCK_HFIFO |\
+				(I3C_BROADCAST_ADDR << 1) |\
+				(NINTH_BIT_ACK << 8))
+#define HFIFO_HEAD		(UNLOCK_HFIFO | INST_WITH_HS |\
+				(NINTH_BIT_ODD_PAR << 8))
+
+#define FIFO_STAT		0xf4
+#define FIFO_DAA		0x0080
+
+#define DMA_INT_FLAG		0x0
+#define DMA_EN			0x08
+#define DMA_RST			0x0c
+#define DMA_CON			0x18
+#define DMA_TX_MEM_ADDR		0x1c
+#define DMA_RX_MEM_ADDR		0x20
+#define DMA_TX_LEN		0x24
+#define DMA_RX_LEN		0x28
+#define DMA_TX_4G_MODE		0x54
+#define DMA_RX_4G_MODE		0x58
+
+#define DMA_EN_START		BIT(0)
+#define DMA_RST_HARD		BIT(1)
+#define DMA_4G_MODE		BIT(0)
+#define DMA_CLR_FLAG		0x0000
+#define DMA_CON_TX		0x0000
+#define DMA_CON_RX		0x0001
+
+#define INTER_CLK_DIV		5
+#define MAX_SAMPLE_CNT		8
+#define MAX_STEP_CNT		64
+#define MAX_HS_STEP_CNT		8
+#define MAX_I3C_DEVS		16
+
+#define MTK_I3C_BCR		0x01
+#define MTK_I3C_DCR		0x02
+#define MTK_I3C_PID		0x03040506
+
+enum mtk_trans_op {
+	MASTER_WR = 1,
+	MASTER_RD,
+	MASTER_WRRD,
+	/* I3C private op */
+	MASTER_CCC_BROADCAST,
+	MASTER_DAA,
+};
+
+enum mtk_trans_mode {
+	I2C_TRANSFER = 1,
+	I3C_TRANSFER,
+	I3C_CCC,
+};
+
+struct daa_addr_ary {
+	u8 addr;
+	bool used;
+};
+
+struct daa_anchor {
+	struct daa_addr_ary daa_addr[MAX_I3C_DEVS];
+	int idx;
+};
+
+struct mtk_i3c_cmd {
+	enum mtk_trans_op op;
+	u8 ccc_id;
+	u16 addr;				/* device addr */
+
+	u16 tx_len;
+	const void *tx_buf;
+	u16 rx_len;
+	void *rx_buf;
+};
+
+struct mtk_i3c_xfer {
+	struct list_head node;
+	struct completion complete;		/* i3c transfer stop */
+
+	int ret;
+	u16 irq_stat;				/* interrupt status */
+	enum mtk_trans_mode mode;
+	bool ignore_restart_irq;
+	bool auto_restart;
+	unsigned int ncmds;
+	unsigned int trans_num;
+	u8 *msg_buf_w;
+	u8 *msg_buf_r;
+	dma_addr_t rpaddr;
+	dma_addr_t wpaddr;
+	struct mtk_i3c_cmd cmds[0];
+};
+
+struct mtk_i3c_master {
+	struct device *dev;
+	struct i3c_master_controller mas_ctrler;
+
+	/* set in i3c probe */
+	void __iomem *regs;			/* i3c base addr */
+	void __iomem *dma_regs;			/* apdma base address*/
+	struct clk *clk_main;			/* main clock for i3c bus */
+	struct clk *clk_dma;			/* DMA clock for i3c via DMA */
+	struct clk *clk_arb;			/* Arbitrator clock for i3c */
+	struct daa_anchor daa_anchor;
+
+	u16 timing_reg[2];
+	u16 ltiming_reg[2];
+	u16 high_speed_reg[2];
+
+	struct {
+		struct list_head list;
+		struct mtk_i3c_xfer *cur;
+		spinlock_t lock;		/* Lock for stats update */
+	} xferqueue;
+};
+
+static u16 mtk_i3c_readw(struct mtk_i3c_master *master, u16 offset)
+{
+	return readw(master->regs + offset);
+}
+
+static void mtk_i3c_writew(struct mtk_i3c_master *master, u16 val, u16 offset)
+{
+	writew(val, master->regs + offset);
+}
+
+/**
+ * Calculate i3c port speed
+ *
+ * Hardware design:
+ * i3c_bus_freq = parent_clk / (clock_div * 2 * sample_cnt * step_cnt)
+ * clock_div: fixed in hardware, but may be various in different SoCs
+ *
+ * The calculation want to pick the highest bus frequency that is still
+ * less than or equal to master->speed_hz. The calculation try to get
+ * sample_cnt and step_cn
+ */
+static int mtk_i3c_calculate_speed(struct mtk_i3c_master *master,
+				   unsigned int clk_src,
+				   unsigned int target_speed,
+				   unsigned int *timing_step_cnt,
+				   unsigned int *timing_sample_cnt)
+{
+	unsigned int sample_cnt, step_cnt, max_step_cnt, opt_div;
+	unsigned int best_mul, cnt_mul, base_step_cnt;
+	unsigned int base_sample_cnt = MAX_SAMPLE_CNT;
+
+	if (target_speed > I3C_BUS_I2C_FM_PLUS_SCL_RATE)
+		max_step_cnt = MAX_HS_STEP_CNT;
+	else
+		max_step_cnt = MAX_STEP_CNT;
+
+	base_step_cnt = max_step_cnt;
+	/* Find the best combination */
+	opt_div = DIV_ROUND_UP(clk_src >> 1, target_speed);
+	best_mul = MAX_SAMPLE_CNT * max_step_cnt;
+
+	/* Search for the best pair (sample_cnt, step_cnt) with
+	 * 1 < sample_cnt < MAX_SAMPLE_CNT
+	 * 1 < step_cnt < max_step_cnt
+	 * sample_cnt * step_cnt >= opt_div
+	 * optimizing for sample_cnt * step_cnt being minimal
+	 */
+	for (sample_cnt = 2; sample_cnt <= MAX_SAMPLE_CNT; sample_cnt++) {
+		step_cnt = DIV_ROUND_UP(opt_div, sample_cnt);
+		cnt_mul = step_cnt * sample_cnt;
+		if (step_cnt > max_step_cnt || step_cnt < 2)
+			continue;
+
+		if (cnt_mul < best_mul) {
+			best_mul = cnt_mul;
+			base_sample_cnt = sample_cnt;
+			base_step_cnt = step_cnt;
+			if (best_mul == opt_div)
+				break;
+		}
+	}
+
+	sample_cnt = base_sample_cnt;
+	step_cnt = base_step_cnt;
+
+	if ((clk_src / (2 * sample_cnt * step_cnt)) > target_speed) {
+		dev_err(master->dev, "Unsupport speed (%uhz)\n", target_speed);
+		return -EINVAL;
+	}
+
+	*timing_step_cnt = step_cnt - 1;
+	*timing_sample_cnt = sample_cnt - 1;
+
+	return 0;
+}
+
+static int mtk_i3c_set_speed(struct mtk_i3c_master *master,
+			     unsigned int clk_src)
+{
+	struct i3c_bus *bus = i3c_master_get_bus(&master->mas_ctrler);
+	unsigned int step_cnt, sample_cnt;
+	int ret;
+
+	ret = mtk_i3c_calculate_speed(master, clk_src, bus->scl_rate.i2c,
+				      &step_cnt, &sample_cnt);
+	if (ret < 0)
+		return ret;
+
+	master->timing_reg[0] = TIMING_VALUE(sample_cnt, step_cnt);
+	/* Disable the high speed transaction */
+	master->high_speed_reg[0] = HS_CLR_VALUE;
+	master->ltiming_reg[0] = LTIMING_VALUE(sample_cnt, step_cnt);
+
+	ret = mtk_i3c_calculate_speed(master, clk_src, bus->scl_rate.i3c,
+				      &step_cnt, &sample_cnt);
+	if (ret < 0)
+		return ret;
+
+	master->timing_reg[1] = TIMING_VALUE(sample_cnt, step_cnt);
+	master->high_speed_reg[1] = HS_VALUE(sample_cnt, step_cnt);
+	master->ltiming_reg[1] = LTIMING_VALUE(sample_cnt, step_cnt);
+
+	return 0;
+}
+
+static inline u32 mtk_i3c_set_4g_mode(dma_addr_t addr)
+{
+	return (addr & BIT_ULL(32)) ? DMA_4G_MODE : DMA_CLR_FLAG;
+}
+
+static inline struct mtk_i3c_master *
+to_mtk_i3c_master(struct i3c_master_controller *master)
+{
+	return container_of(master, struct mtk_i3c_master, mas_ctrler);
+}
+
+static u8 mtk_i3c_master_get_free_addr(struct mtk_i3c_master *master)
+{
+	int slot = 0;
+
+	for (slot = 0; slot < MAX_I3C_DEVS; slot++) {
+		if (master->daa_anchor.daa_addr[slot].used)
+			continue;
+
+		master->daa_anchor.idx = slot;
+		master->daa_anchor.daa_addr[slot].used = true;
+		return master->daa_anchor.daa_addr[slot].addr;
+	}
+
+	return 0;
+}
+
+static int mtk_i3c_master_clock_enable(struct mtk_i3c_master *master)
+{
+	int ret;
+
+	ret = clk_prepare_enable(master->clk_dma);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(master->clk_main);
+	if (ret)
+		goto err_main;
+
+	ret = clk_prepare_enable(master->clk_arb);
+	if (ret)
+		goto err_arb;
+
+	return 0;
+
+err_arb:
+	clk_disable_unprepare(master->clk_main);
+err_main:
+	clk_disable_unprepare(master->clk_dma);
+
+	return ret;
+}
+
+static void mtk_i3c_master_clock_disable(struct mtk_i3c_master *master)
+{
+	clk_disable_unprepare(master->clk_arb);
+	clk_disable_unprepare(master->clk_main);
+	clk_disable_unprepare(master->clk_dma);
+}
+
+static inline void mtk_i3c_master_init_hw(struct mtk_i3c_master *master)
+{
+	struct mtk_i3c_xfer *mtk_xfer = master->xferqueue.cur;
+
+	if (!mtk_xfer->trans_num)
+		mtk_i3c_writew(master, SOFT_RST, SOFTRESET);
+
+	mtk_i3c_writew(master, IO_CONFIG_PUSH_PULL, IO_CONFIG);
+	mtk_i3c_writew(master, CLOCK_DIV_DEFAULT, CLOCK_DIV);
+
+	mtk_i3c_writew(master, DELAY_LEN_DEFAULT, DELAY_LEN);
+	mtk_i3c_writew(master, EXT_CONF_DEFAULT, EXT_CONF);
+	mtk_i3c_writew(master, FIFO_CLR, FIFO_ADDR_CLR);
+
+	/* DMA hard reset */
+	writel(DMA_RST_HARD, master->dma_regs + DMA_RST);
+	writel(DMA_CLR_FLAG, master->dma_regs + DMA_RST);
+}
+
+static int mtk_i3c_master_apdma_tx(struct mtk_i3c_master *master)
+{
+	struct mtk_i3c_xfer *xfer = master->xferqueue.cur;
+	struct mtk_i3c_cmd *cmd = &xfer->cmds[xfer->trans_num];
+	u32 reg_4g_mode;
+
+	xfer->msg_buf_w = kzalloc(cmd->tx_len, GFP_KERNEL);
+	if (!xfer->msg_buf_w)
+		return -ENOMEM;
+
+	memcpy(xfer->msg_buf_w, cmd->tx_buf, cmd->tx_len);
+
+	writel(DMA_CLR_FLAG, master->dma_regs + DMA_INT_FLAG);
+	writel(DMA_CON_TX, master->dma_regs + DMA_CON);
+
+	xfer->wpaddr = dma_map_single(master->dev, xfer->msg_buf_w,
+				      cmd->tx_len, DMA_TO_DEVICE);
+	if (dma_mapping_error(master->dev, xfer->wpaddr)) {
+		kfree(xfer->msg_buf_w);
+		return -ENOMEM;
+	}
+
+	reg_4g_mode = mtk_i3c_set_4g_mode(xfer->wpaddr);
+	writel(reg_4g_mode, master->dma_regs + DMA_TX_4G_MODE);
+	writel((u32)xfer->wpaddr, master->dma_regs + DMA_TX_MEM_ADDR);
+	writel(cmd->tx_len, master->dma_regs + DMA_TX_LEN);
+
+	return 0;
+}
+
+static int mtk_i3c_master_apdma_rx(struct mtk_i3c_master *master)
+{
+	struct mtk_i3c_xfer *xfer = master->xferqueue.cur;
+	struct mtk_i3c_cmd *cmd = &xfer->cmds[xfer->trans_num];
+	u32 reg_4g_mode;
+
+	xfer->msg_buf_r = kzalloc(cmd->rx_len, GFP_KERNEL);
+	if (!xfer->msg_buf_r)
+		return -ENOMEM;
+
+	writel(DMA_CLR_FLAG, master->dma_regs + DMA_INT_FLAG);
+	writel(DMA_CON_RX, master->dma_regs + DMA_CON);
+
+	xfer->rpaddr = dma_map_single(master->dev, xfer->msg_buf_r,
+				      cmd->rx_len, DMA_FROM_DEVICE);
+	if (dma_mapping_error(master->dev, xfer->rpaddr)) {
+		kfree(xfer->msg_buf_r);
+		return -ENOMEM;
+	}
+
+	reg_4g_mode = mtk_i3c_set_4g_mode(xfer->rpaddr);
+	writel(reg_4g_mode, master->dma_regs + DMA_RX_4G_MODE);
+	writel((u32)xfer->rpaddr, master->dma_regs + DMA_RX_MEM_ADDR);
+	writel(cmd->rx_len, master->dma_regs + DMA_RX_LEN);
+
+	return 0;
+}
+
+static void mtk_i3c_master_apdma_end(struct mtk_i3c_master *master)
+{
+	struct mtk_i3c_xfer *xfer = master->xferqueue.cur;
+	struct mtk_i3c_cmd *cmd = &xfer->cmds[xfer->trans_num];
+	u16 int_reg = 0;
+
+	if (xfer->auto_restart)
+		int_reg = INTR_RS_MULTI;
+
+	int_reg |= INTR_ALL;
+
+	/* Clear interrupt mask */
+	mtk_i3c_writew(master, ~int_reg, INTR_MASK);
+
+	if (cmd->op == MASTER_WR) {
+		dma_unmap_single(master->dev, xfer->wpaddr,
+				 cmd->tx_len, DMA_TO_DEVICE);
+		kfree(xfer->msg_buf_w);
+
+	} else if (cmd->op == MASTER_RD) {
+		dma_unmap_single(master->dev, xfer->rpaddr,
+				 cmd->rx_len, DMA_FROM_DEVICE);
+		memcpy(cmd->rx_buf, xfer->msg_buf_r, cmd->rx_len);
+		kfree(xfer->msg_buf_r);
+	} else if (cmd->op == MASTER_WRRD) {
+		dma_unmap_single(master->dev, xfer->wpaddr, cmd->tx_len,
+				 DMA_TO_DEVICE);
+		dma_unmap_single(master->dev, xfer->rpaddr, cmd->rx_len,
+				 DMA_FROM_DEVICE);
+		memcpy(cmd->rx_buf, xfer->msg_buf_r, cmd->rx_len);
+		kfree(xfer->msg_buf_w);
+		kfree(xfer->msg_buf_r);
+	}
+}
+
+static int mtk_i3c_master_start_transfer(struct mtk_i3c_master *master)
+{
+	struct mtk_i3c_xfer *xfer = master->xferqueue.cur;
+	struct mtk_i3c_cmd *cmd = &xfer->cmds[xfer->trans_num];
+	u16 addr_reg, control_reg, start_reg, tmp_rg_val;
+	u16 int_reg = 0, data_size;
+	int ret = 0;
+	u8 *ptr;
+
+	xfer->irq_stat = 0;
+
+	mtk_i3c_master_init_hw(master);
+
+	control_reg = CONTROL_ACKERR_DET_EN | CONTROL_CLK_EXT_EN | CONTROL_RS;
+	if (cmd->op != MASTER_DAA)
+		control_reg |= CONTROL_DMA_EN | CONTROL_DMAACK_EN |
+			       CONTROL_ASYNC_MODE;
+
+	if (cmd->op == MASTER_WRRD)
+		control_reg |= CONTROL_DIR_CHANGE;
+
+	mtk_i3c_writew(master, control_reg, CONTROL);
+
+	addr_reg = cmd->addr << 1;
+	if (cmd->op == MASTER_RD || cmd->op == MASTER_DAA)
+		addr_reg |= 0x1;
+
+	mtk_i3c_writew(master, addr_reg, SLAVE_ADDR);
+
+	if (xfer->auto_restart)
+		int_reg = INTR_RS_MULTI;
+
+	int_reg |= INTR_ALL;
+	/* Clear interrupt status */
+	mtk_i3c_writew(master, int_reg, INTR_STAT);
+	/* Enable interrupt */
+	mtk_i3c_writew(master, int_reg, INTR_MASK);
+
+	/* Set transfer and transaction len */
+	switch (cmd->op) {
+	case MASTER_WRRD:
+		mtk_i3c_writew(master, cmd->tx_len, TRANSFER_LEN);
+		mtk_i3c_writew(master, cmd->rx_len, TRANSFER_LEN_AUX);
+		mtk_i3c_writew(master, TRANSAC_LEN_WRRD, TRANSAC_LEN);
+		break;
+	case MASTER_WR:
+		mtk_i3c_writew(master, cmd->tx_len, TRANSFER_LEN);
+		mtk_i3c_writew(master, xfer->ncmds, TRANSAC_LEN);
+		break;
+	case MASTER_RD:
+		mtk_i3c_writew(master, cmd->rx_len, TRANSFER_LEN);
+		mtk_i3c_writew(master, xfer->ncmds, TRANSAC_LEN);
+		break;
+	case MASTER_CCC_BROADCAST:
+		mtk_i3c_writew(master, TRANS_ONE_LEN, TRANSFER_LEN);
+		mtk_i3c_writew(master, TRANS_ONE_LEN, TRANSAC_LEN);
+		break;
+	case MASTER_DAA:
+		mtk_i3c_writew(master, cmd->rx_len + cmd->tx_len, TRANSFER_LEN);
+		mtk_i3c_writew(master, MAX_I3C_DEVS, TRANSAC_LEN);
+		break;
+	}
+
+	mtk_i3c_writew(master, master->timing_reg[1], TIMING);
+	mtk_i3c_writew(master, master->ltiming_reg[1], LTIMING);
+	mtk_i3c_writew(master, master->high_speed_reg[1], HS);
+
+	if (xfer->mode == I2C_TRANSFER) {
+		mtk_i3c_writew(master, master->timing_reg[0], TIMING);
+		mtk_i3c_writew(master, master->ltiming_reg[0], LTIMING);
+		mtk_i3c_writew(master, master->high_speed_reg[0], HS);
+		mtk_i3c_writew(master, 0, TRAFFIC);
+		mtk_i3c_writew(master, 0, SHAPE);
+	} else if ((xfer->mode == I3C_TRANSFER) && (!xfer->trans_num)) {
+		mtk_i3c_writew(master, HFIFO_DATA_7E, HFIFO_DATA);
+		mtk_i3c_writew(master, TRAFFIC_HANDOFF | TRAFFIC_TBIT, TRAFFIC);
+		mtk_i3c_writew(master, SHAPE_T_STALL | SHAPE_T_PARITY, SHAPE);
+	} else if (xfer->mode == I3C_CCC) {
+		tmp_rg_val = TRAFFIC_HANDOFF | TRAFFIC_TBIT;
+		mtk_i3c_writew(master, HFIFO_DATA_7E, HFIFO_DATA);
+		mtk_i3c_writew(master, HFIFO_HEAD | cmd->ccc_id, HFIFO_DATA);
+
+		if (cmd->op == MASTER_DAA) {
+			addr_reg = mtk_i3c_master_get_free_addr(master);
+			addr_reg |= DEF_DAA_SLV_PARITY;
+			tmp_rg_val |= TRAFFIC_DAA_EN;
+			mtk_i3c_writew(master, addr_reg, DEF_DA);
+		} else if (cmd->op == MASTER_CCC_BROADCAST) {
+			tmp_rg_val |= TRAFFIC_HEAD_ONLY | TRAFFIC_SKIP_SLV_ADDR;
+			data_size = cmd->tx_len;
+			ptr = (u8 *)cmd->tx_buf;
+			while (data_size--)
+				mtk_i3c_writew(master, HFIFO_HEAD | *ptr++,
+					       HFIFO_DATA);
+		}
+
+		mtk_i3c_writew(master, tmp_rg_val, TRAFFIC);
+		mtk_i3c_writew(master, SHAPE_T_STALL, SHAPE);
+	}
+
+	/* Prepare buffer data to start transfer */
+	if (cmd->op == MASTER_RD) {
+		ret = mtk_i3c_master_apdma_rx(master);
+	} else if (cmd->op == MASTER_WR) {
+		ret = mtk_i3c_master_apdma_tx(master);
+	} else if (cmd->op == MASTER_WRRD) {
+		ret = mtk_i3c_master_apdma_rx(master);
+		ret |= mtk_i3c_master_apdma_tx(master);
+	}
+
+	if (ret)
+		return ret;
+
+	if (cmd->op != MASTER_DAA)
+		writel(DMA_EN_START, master->dma_regs + DMA_EN);
+
+	start_reg = START_EN;
+	if (xfer->auto_restart) {
+		start_reg |= START_MUL_TRIG;
+		if ((xfer->ncmds - xfer->trans_num) >= 2 ||
+		    cmd->op == MASTER_DAA)
+			start_reg |= START_MUL_CNFG;
+	}
+
+	mtk_i3c_writew(master, MCU_INTR_EN, MCU_INTR);
+	mtk_i3c_writew(master, start_reg, START);
+
+	return 0;
+}
+
+static struct mtk_i3c_xfer *
+mtk_i3c_master_alloc_xfer(struct mtk_i3c_master *master, unsigned int ncmds)
+{
+	struct mtk_i3c_xfer *xfer;
+
+	xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL);
+	if (!xfer)
+		return NULL;
+
+	INIT_LIST_HEAD(&xfer->node);
+	xfer->ncmds = ncmds;
+	xfer->ret = -ETIMEDOUT;
+
+	return xfer;
+}
+
+static void mtk_i3c_master_free_xfer(struct mtk_i3c_xfer *xfer)
+{
+	kfree(xfer);
+}
+
+static void mtk_i3c_master_start_xfer_locked(struct mtk_i3c_master *master)
+{
+	struct mtk_i3c_xfer *xfer = master->xferqueue.cur;
+	unsigned int ret = 0;
+
+	if (!xfer)
+		return;
+
+	xfer->trans_num = 0;
+	ret = mtk_i3c_master_start_transfer(master);
+	xfer->ret = ret;
+}
+
+static void mtk_i3c_master_end_xfer_locked(struct mtk_i3c_master *master)
+{
+	struct mtk_i3c_xfer *xfer = master->xferqueue.cur;
+	struct mtk_i3c_cmd *cmd = &xfer->cmds[xfer->trans_num];
+	int slot = 0;
+
+	if (!xfer)
+		return;
+
+	if (xfer->irq_stat & (INTR_ACKERR | INTR_MAS_ERR)) {
+		if (cmd->op == MASTER_DAA) {
+			slot = master->daa_anchor.idx;
+			master->daa_anchor.daa_addr[slot].used = false;
+		}
+
+		dev_err(master->dev, "Addr: %x, device ACK error\n", cmd->addr);
+		xfer->ret = -ENXIO;
+	}
+
+	mtk_i3c_master_apdma_end(master);
+
+	complete(&xfer->complete);
+	xfer = list_first_entry_or_null(&master->xferqueue.list,
+					struct mtk_i3c_xfer, node);
+	if (xfer)
+		list_del_init(&xfer->node);
+
+	master->xferqueue.cur = xfer;
+	mtk_i3c_master_start_xfer_locked(master);
+}
+
+static irqreturn_t mtk_i3c_master_irq(int irqno, void *dev_id)
+{
+	struct mtk_i3c_master *master = dev_id;
+	struct mtk_i3c_xfer *xfer = master->xferqueue.cur;
+	struct mtk_i3c_cmd *cmd = &xfer->cmds[xfer->trans_num];
+	u16 restart_flag = 0, intr_stat, fifo_stat;
+	unsigned long flags;
+	int ret = 0;
+	u8 addr;
+
+	if (cmd->op == MASTER_DAA) {
+		fifo_stat = mtk_i3c_readw(master, FIFO_STAT);
+		intr_stat = mtk_i3c_readw(master, INTR_STAT);
+
+		if (!(fifo_stat & FIFO_DAA) && !(intr_stat & INTR_TRANSAC_COMP))
+			return IRQ_NONE;
+
+		mtk_i3c_writew(master, FIFO_CLR, FIFO_ADDR_CLR);
+	}
+
+	if (xfer->auto_restart)
+		restart_flag = INTR_RS_MULTI;
+
+	intr_stat = mtk_i3c_readw(master, INTR_STAT);
+	mtk_i3c_writew(master, intr_stat, INTR_STAT);
+
+	spin_lock_irqsave(&master->xferqueue.lock, flags);
+	/*
+	 * when occurs ack error, i3c controller generate two interrupts
+	 * first is the ack error interrupt, then the complete interrupt
+	 * i3c->irq_stat need keep the two interrupt value.
+	 */
+	xfer->irq_stat |= intr_stat;
+
+	if (xfer->irq_stat & INTR_TRANSAC_COMP) {
+		mtk_i3c_master_end_xfer_locked(master);
+		goto exit_irq;
+	}
+
+	if (xfer->irq_stat & restart_flag) {
+		xfer->irq_stat = 0;
+
+		if (xfer->ignore_restart_irq) {
+			xfer->ignore_restart_irq = false;
+
+			mtk_i3c_writew(master, START_MUL_CNFG | START_MUL_TRIG |
+					       START_EN, START);
+		} else if (cmd->op == MASTER_DAA) {
+			addr = mtk_i3c_master_get_free_addr(master);
+			mtk_i3c_writew(master, addr, DEF_DA);
+			mtk_i3c_writew(master, START_MUL_CNFG | START_MUL_TRIG |
+					       START_EN, START);
+		} else {
+			mtk_i3c_master_apdma_end(master);
+
+			xfer->trans_num++;
+			ret = mtk_i3c_master_start_transfer(master);
+			xfer->ret = ret;
+		}
+	}
+
+exit_irq:
+	spin_unlock_irqrestore(&master->xferqueue.lock, flags);
+	return IRQ_HANDLED;
+}
+
+static void mtk_i3c_master_queue_xfer(struct mtk_i3c_master *master,
+				      struct mtk_i3c_xfer *xfer)
+{
+	unsigned long flags;
+
+	init_completion(&xfer->complete);
+	spin_lock_irqsave(&master->xferqueue.lock, flags);
+	if (master->xferqueue.cur) {
+		list_add_tail(&xfer->node, &master->xferqueue.list);
+	} else {
+		master->xferqueue.cur = xfer;
+		mtk_i3c_master_start_xfer_locked(master);
+	}
+	spin_unlock_irqrestore(&master->xferqueue.lock, flags);
+}
+
+static void mtk_i3c_master_unqueue_xfer(struct mtk_i3c_master *master,
+					struct mtk_i3c_xfer *xfer)
+{
+	struct mtk_i3c_cmd *ccmd = &xfer->cmds[xfer->trans_num];
+	unsigned long flags;
+
+	dev_err(master->dev, "addr: %x, transfer timeout\n", ccmd->addr);
+	xfer->ret = -ETIMEDOUT;
+
+	spin_lock_irqsave(&master->xferqueue.lock, flags);
+	mtk_i3c_master_apdma_end(master);
+	if (master->xferqueue.cur == xfer)
+		master->xferqueue.cur = NULL;
+	else
+		list_del_init(&xfer->node);
+	spin_unlock_irqrestore(&master->xferqueue.lock, flags);
+}
+
+static bool mtk_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
+					    const struct i3c_ccc_cmd *cmd)
+{
+	if (cmd->ndests > 1)
+		return false;
+
+	switch (cmd->id) {
+	case I3C_CCC_ENEC(true):
+	case I3C_CCC_ENEC(false):
+	case I3C_CCC_DISEC(true):
+	case I3C_CCC_DISEC(false):
+	case I3C_CCC_ENTAS(0, true):
+	case I3C_CCC_ENTAS(0, false):
+	case I3C_CCC_RSTDAA(true):
+	case I3C_CCC_RSTDAA(false):
+	/* FALL THROUGH */
+	case I3C_CCC_ENTDAA:
+	case I3C_CCC_SETMWL(true):
+	case I3C_CCC_SETMWL(false):
+	case I3C_CCC_SETMRL(true):
+	case I3C_CCC_SETMRL(false):
+	/* FALL THROUGH */
+	case I3C_CCC_DEFSLVS:
+	case I3C_CCC_ENTHDR(0):
+	case I3C_CCC_SETDASA:
+	case I3C_CCC_SETNEWDA:
+	case I3C_CCC_GETMWL:
+	case I3C_CCC_GETMRL:
+	case I3C_CCC_GETPID:
+	case I3C_CCC_GETBCR:
+	case I3C_CCC_GETDCR:
+	case I3C_CCC_GETSTATUS:
+	case I3C_CCC_GETACCMST:
+	case I3C_CCC_GETMXDS:
+	case I3C_CCC_GETHDRCAP:
+		return true;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static int mtk_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
+				       struct i3c_ccc_cmd *cmd)
+{
+	struct mtk_i3c_master *master = to_mtk_i3c_master(m);
+	struct mtk_i3c_xfer *mtk_xfer;
+	struct mtk_i3c_cmd *ccmd;
+	int ret = 0;
+
+	mtk_xfer = mtk_i3c_master_alloc_xfer(master, 1);
+	if (!mtk_xfer)
+		return -ENOMEM;
+
+	mtk_xfer->mode = I3C_CCC;
+	mtk_xfer->ignore_restart_irq = false;
+	mtk_xfer->auto_restart = false;
+
+	ccmd = mtk_xfer->cmds;
+	ccmd->addr = cmd->dests[0].addr;
+	ccmd->ccc_id = cmd->id;
+
+	if (cmd->rnw) {
+		ccmd->op = MASTER_RD;
+		ccmd->rx_len = cmd->dests[0].payload.len;
+		ccmd->rx_buf = cmd->dests[0].payload.data;
+	} else {
+		ccmd->op = MASTER_WR;
+		ccmd->tx_len = cmd->dests[0].payload.len;
+		ccmd->tx_buf = cmd->dests[0].payload.data;
+	}
+
+	if (ccmd->ccc_id < I3C_CCC_DIRECT) {
+		ccmd->op = MASTER_CCC_BROADCAST;
+		if (ccmd->ccc_id == I3C_CCC_ENTDAA) {
+			ccmd->op = MASTER_DAA;
+			ccmd->rx_len = 8;
+			ccmd->tx_len = 1;
+			mtk_xfer->ignore_restart_irq = true;
+			mtk_xfer->auto_restart = true;
+		}
+	}
+
+	mtk_i3c_master_queue_xfer(master, mtk_xfer);
+	if (!wait_for_completion_timeout(&mtk_xfer->complete,
+					 msecs_to_jiffies(2000)))
+		mtk_i3c_master_unqueue_xfer(master, mtk_xfer);
+
+	ret = mtk_xfer->ret;
+
+	mtk_i3c_master_free_xfer(mtk_xfer);
+	if (ret == -ENXIO)
+		return I3C_ERROR_M2;
+
+	return ret;
+}
+
+static int mtk_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
+				     struct i3c_priv_xfer *xfers,
+				     int nxfers)
+{
+	struct i3c_master_controller *m = i3c_dev_get_master(dev);
+	struct mtk_i3c_master *master = to_mtk_i3c_master(m);
+	struct mtk_i3c_xfer *mtk_xfer;
+	int i, ret = 0;
+
+	if (!nxfers)
+		return 0;
+
+	mtk_xfer = mtk_i3c_master_alloc_xfer(master, nxfers);
+	if (!mtk_xfer)
+		return -ENOMEM;
+
+	mtk_xfer->mode = I3C_TRANSFER;
+
+	if (nxfers == 2 && !xfers[0].rnw && xfers[1].rnw)
+		mtk_xfer->auto_restart = false;
+	else
+		mtk_xfer->auto_restart = true;
+
+	if (mtk_xfer->auto_restart && nxfers >= 2)
+		mtk_xfer->ignore_restart_irq = true;
+	else
+		mtk_xfer->ignore_restart_irq = false;
+
+	for (i = 0; i < nxfers; i++) {
+		struct mtk_i3c_cmd *ccmd = &mtk_xfer->cmds[i];
+
+		ccmd->addr = dev->info.dyn_addr;
+		ccmd->ccc_id = 0;
+
+		if (!mtk_xfer->auto_restart) {
+			/* combined two messages into one transaction */
+			ccmd->op = MASTER_WRRD;
+			ccmd->tx_len = xfers[i].len;
+			ccmd->rx_len = xfers[i + 1].len;
+			ccmd->tx_buf = xfers->data.out;
+			ccmd->rx_buf = xfers[i + 1].data.in;
+			break;
+		}
+
+		if (xfers[i].rnw) {
+			ccmd->op = MASTER_RD;
+			ccmd->rx_len = xfers->len;
+			ccmd->rx_buf = xfers->data.in;
+		} else {
+			ccmd->op = MASTER_WR;
+			ccmd->tx_len = xfers->len;
+			ccmd->tx_buf = xfers->data.out;
+		}
+	}
+
+	mtk_i3c_master_queue_xfer(master, mtk_xfer);
+	if (!wait_for_completion_timeout(&mtk_xfer->complete,
+					 msecs_to_jiffies(2000)))
+		mtk_i3c_master_unqueue_xfer(master, mtk_xfer);
+
+	ret = mtk_xfer->ret;
+
+	mtk_i3c_master_free_xfer(mtk_xfer);
+	return ret;
+}
+
+static int mtk_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
+				    const struct i2c_msg *xfers,
+				    int nxfers)
+{
+	struct i3c_master_controller *m = i2c_dev_get_master(dev);
+	struct mtk_i3c_master *master = to_mtk_i3c_master(m);
+	struct mtk_i3c_xfer *mtk_xfer;
+	int i, ret = 0;
+
+	mtk_xfer = mtk_i3c_master_alloc_xfer(master, nxfers);
+	if (!mtk_xfer)
+		return -ENOMEM;
+
+	mtk_xfer->mode = I2C_TRANSFER;
+
+	if (!(xfers[0].flags & I2C_M_RD) && (xfers[1].flags & I2C_M_RD) &&
+	    xfers[0].addr == xfers[1].addr && nxfers == 2)
+		mtk_xfer->auto_restart = false;
+	else
+		mtk_xfer->auto_restart = true;
+
+	mtk_xfer->ignore_restart_irq = false;
+
+	for (i = 0; i < nxfers; i++) {
+		struct mtk_i3c_cmd *ccmd = &mtk_xfer->cmds[i];
+
+		ccmd->addr = xfers->addr;
+		ccmd->ccc_id = 0;
+
+		if (!mtk_xfer->auto_restart) {
+			/* combined two messages into one transaction */
+			ccmd->op = MASTER_WRRD;
+			ccmd->tx_len = xfers[i].len;
+			ccmd->rx_len = xfers[i + 1].len;
+			ccmd->tx_buf = xfers[i].buf;
+			ccmd->rx_buf = xfers[i + 1].buf;
+			break;
+		}
+
+		if (xfers[i].flags & I2C_M_RD) {
+			ccmd->op = MASTER_RD;
+			ccmd->rx_len = xfers[i].len;
+			ccmd->rx_buf = xfers[i].buf;
+		} else {
+			ccmd->op = MASTER_WR;
+			ccmd->tx_len = xfers[i].len;
+			ccmd->tx_buf = xfers[i].buf;
+		}
+	}
+
+	mtk_i3c_master_queue_xfer(master, mtk_xfer);
+	if (!wait_for_completion_timeout(&mtk_xfer->complete,
+					 msecs_to_jiffies(2000)))
+		mtk_i3c_master_unqueue_xfer(master, mtk_xfer);
+
+	ret = mtk_xfer->ret;
+
+	mtk_i3c_master_free_xfer(mtk_xfer);
+	return ret;
+}
+
+static u32 mtk_i3c_master_i2c_funcs(struct i3c_master_controller *m)
+{
+	return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C;
+}
+
+static int mtk_i3c_master_do_daa(struct i3c_master_controller *m)
+{
+	struct mtk_i3c_master *master = to_mtk_i3c_master(m);
+	int ret = 0, slot;
+	u8 last_addr = 0;
+
+	for (slot = 0; slot < MAX_I3C_DEVS; slot++) {
+		if (master->daa_anchor.daa_addr[slot].used)
+			continue;
+
+		ret = i3c_master_get_free_addr(m, last_addr + 1);
+		if (ret < 0)
+			return -ENOSPC;
+
+		last_addr = ret;
+		master->daa_anchor.daa_addr[slot].addr = last_addr;
+	}
+
+	ret = i3c_master_entdaa_locked(&master->mas_ctrler);
+	if (ret && ret != I3C_ERROR_M2)
+		return ret;
+
+	/*
+	 * Clear all retaining registers filled during DAA. We already
+	 * have the addressed assigned to them in the addrs array.
+	 */
+	for (slot = 1; slot < MAX_I3C_DEVS; slot++) {
+		if (master->daa_anchor.daa_addr[slot].used) {
+			last_addr = master->daa_anchor.daa_addr[slot].addr;
+			i3c_master_add_i3c_dev_locked(m, last_addr);
+		}
+	}
+
+	/*
+	 * Clear slots that ended up not being used. Can be caused by I3C
+	 * device creation failure or when the I3C device was already known
+	 * by the system but with a different address (in this case the device
+	 * already has a slot and does not need a new one).
+	 */
+	i3c_master_defslvs_locked(&master->mas_ctrler);
+
+	/* Unmask Hot-Join and Mastership request interrupts. */
+	i3c_master_enec_locked(m, I3C_BROADCAST_ADDR, I3C_CCC_EVENT_HJ |
+				  I3C_CCC_EVENT_MR);
+
+	return 0;
+}
+
+static int mtk_i3c_master_bus_init(struct i3c_master_controller *m)
+{
+	struct mtk_i3c_master *master = to_mtk_i3c_master(m);
+	struct i3c_device_info info = { };
+	unsigned long source_clk_rate;
+	int ret;
+
+	source_clk_rate = clk_get_rate(master->clk_main) / INTER_CLK_DIV;
+
+	ret = mtk_i3c_set_speed(master, source_clk_rate);
+	if (ret) {
+		dev_err(master->dev, "Failed to set the bus speed.\n");
+		return -EINVAL;
+	}
+
+	/* Get an address for the master. */
+	ret = i3c_master_get_free_addr(m, 0);
+	if (ret < 0)
+		return ret;
+
+	master->daa_anchor.daa_addr[0].addr = (u8)ret;
+	master->daa_anchor.daa_addr[0].used = true;
+	master->daa_anchor.idx = 0;
+
+	/*
+	 * In I3C protocol host controller is also with device role,
+	 * so the driver should provide dcr, bcr, and pid info
+	 * of host controller itself
+	 */
+	memset(&info, 0, sizeof(info));
+	info.dyn_addr = ret;
+	info.dcr = MTK_I3C_DCR;
+	info.bcr = MTK_I3C_BCR;
+	info.pid = MTK_I3C_PID;
+
+	if (info.bcr & I3C_BCR_HDR_CAP)
+		info.hdr_cap = I3C_CCC_HDR_MODE(I3C_HDR_DDR);
+
+	return i3c_master_set_info(&master->mas_ctrler, &info);
+}
+
+static const struct i3c_master_controller_ops mtk_i3c_master_ops = {
+	.bus_init = mtk_i3c_master_bus_init,
+	.do_daa = mtk_i3c_master_do_daa,
+	.supports_ccc_cmd = mtk_i3c_master_supports_ccc_cmd,
+	.send_ccc_cmd = mtk_i3c_master_send_ccc_cmd,
+	.priv_xfers = mtk_i3c_master_priv_xfers,
+	.i2c_xfers = mtk_i3c_master_i2c_xfers,
+	.i2c_funcs = mtk_i3c_master_i2c_funcs,
+};
+
+static int mtk_i3c_master_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_i3c_master *master;
+	struct resource *res;
+	int ret, irqnr;
+
+	master = devm_kzalloc(dev, sizeof(*master), GFP_KERNEL);
+	if (!master)
+		return -ENOMEM;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "main");
+	master->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(master->regs))
+		return PTR_ERR(master->regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma");
+	master->dma_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(master->dma_regs))
+		return PTR_ERR(master->dma_regs);
+
+	irqnr = platform_get_irq(pdev, 0);
+	if (irqnr < 0)
+		return irqnr;
+
+	ret = devm_request_irq(dev, irqnr, mtk_i3c_master_irq,
+			       IRQF_TRIGGER_NONE, DRV_NAME, master);
+	if (ret < 0) {
+		dev_err(dev, "Request I3C IRQ %d fail\n", irqnr);
+		return ret;
+	}
+
+	spin_lock_init(&master->xferqueue.lock);
+	INIT_LIST_HEAD(&master->xferqueue.list);
+
+	if (dma_set_mask(dev, DMA_BIT_MASK(33))) {
+		dev_err(dev, "dma_set_mask return error.\n");
+		return -EINVAL;
+	}
+
+	master->clk_main = devm_clk_get(dev, "main");
+	if (IS_ERR(master->clk_main)) {
+		dev_err(dev, "cannot get main clock\n");
+		return PTR_ERR(master->clk_main);
+	}
+	master->clk_dma = devm_clk_get(dev, "dma");
+	if (IS_ERR(master->clk_dma)) {
+		dev_err(dev, "cannot get dma clock\n");
+		return PTR_ERR(master->clk_dma);
+	}
+
+	master->clk_arb = devm_clk_get_optional(dev, "arb");
+	if (IS_ERR(master->clk_arb))
+		return PTR_ERR(master->clk_arb);
+
+	ret = mtk_i3c_master_clock_enable(master);
+	if (ret) {
+		dev_err(dev, "clock enable failed!\n");
+		return ret;
+	}
+
+	master->dev = dev;
+	platform_set_drvdata(pdev, master);
+
+	ret = i3c_master_register(&master->mas_ctrler, dev,
+				  &mtk_i3c_master_ops, false);
+	if (ret) {
+		dev_err(dev, "Failed to add i3c bus to i3c core\n");
+		mtk_i3c_master_clock_disable(master);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int mtk_i3c_master_remove(struct platform_device *pdev)
+{
+	struct mtk_i3c_master *master = platform_get_drvdata(pdev);
+
+	i3c_master_unregister(&master->mas_ctrler);
+	mtk_i3c_master_clock_disable(master);
+
+	return 0;
+}
+
+static const struct of_device_id mtk_i3c_master_of_ids[] = {
+	{ .compatible = "mediatek,i3c-master" },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mtk_i3c_master_of_ids);
+
+static struct platform_driver mtk_i3c_master_driver = {
+	.probe = mtk_i3c_master_probe,
+	.remove = mtk_i3c_master_remove,
+	.driver = {
+		.name = DRV_NAME,
+		.of_match_table = mtk_i3c_master_of_ids,
+	},
+};
+module_platform_driver(mtk_i3c_master_driver);
+
+MODULE_AUTHOR("Qii Wang <qii.wang@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek I3C master driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mtk-i3c-master");
-- 
1.7.9.5

^ permalink raw reply related

* [PATCH v3 1/2] dt-bindings: i3c: Document MediaTek I3C master bindings
From: Qii Wang @ 2019-07-09 13:09 UTC (permalink / raw)
  To: bbrezillon
  Cc: matthias.bgg, robh+dt, mark.rutland, linux-i3c, devicetree,
	linux-arm-kernel, linux-kernel, linux-mediatek, srv_heupstream,
	leilk.liu, qii.wang, liguo.zhang, xinping.qian
In-Reply-To: <1562677762-24067-1-git-send-email-qii.wang@mediatek.com>

Document MediaTek I3C master DT bindings.

Signed-off-by: Qii Wang <qii.wang@mediatek.com>
---
 .../devicetree/bindings/i3c/mtk,i3c-master.txt     |   48 ++++++++++++++++++++
 1 file changed, 48 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i3c/mtk,i3c-master.txt

diff --git a/Documentation/devicetree/bindings/i3c/mtk,i3c-master.txt b/Documentation/devicetree/bindings/i3c/mtk,i3c-master.txt
new file mode 100644
index 0000000..d32eda6
--- /dev/null
+++ b/Documentation/devicetree/bindings/i3c/mtk,i3c-master.txt
@@ -0,0 +1,48 @@
+Bindings for MediaTek I3C master block
+=====================================
+
+Required properties:
+--------------------
+- compatible: shall be "mediatek,i3c-master"
+- reg: physical base address of the controller and apdma base, length of
+  memory mapped region.
+- reg-names: shall be "main" for master controller and "dma" for apdma.
+- interrupts: the interrupt line connected to this I3C master.
+- clocks: shall reference the i3c and apdma clocks.
+- clock-names: shall include "main" and "dma".
+
+Mandatory properties defined by the generic binding (see
+Documentation/devicetree/bindings/i3c/i3c.txt for more details):
+
+- #address-cells: shall be set to 3
+- #size-cells: shall be set to 0
+
+Optional properties defined by the generic binding (see
+Documentation/devicetree/bindings/i3c/i3c.txt for more details):
+
+- i2c-scl-hz
+- i3c-scl-hz
+
+I3C device connected on the bus follow the generic description (see
+Documentation/devicetree/bindings/i3c/i3c.txt for more details).
+
+Example:
+
+	i3c0: i3c@1100d000 {
+		compatible = "mediatek,i3c-master";
+		reg = <0x1100d000 0x1000>,
+		      <0x11000300 0x80>;
+		reg-names = "main", "dma";
+		interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_LOW>;
+		clocks = <&infracfg CLK_INFRA_I3C0>,
+			 <&infracfg CLK_INFRA_AP_DMA>;
+		clock-names = "main", "dma";
+		#address-cells = <3>;
+		#size-cells = <0>;
+		i2c-scl-hz = <100000>;
+
+		nunchuk: nunchuk@52 {
+			compatible = "nintendo,nunchuk";
+			reg = <0x52 0x0 0x10>;
+		};
+	};
-- 
1.7.9.5

^ permalink raw reply related

* [PATCH v3 0/2] Add MediaTek I3C master controller driver
From: Qii Wang @ 2019-07-09 13:09 UTC (permalink / raw)
  To: bbrezillon
  Cc: matthias.bgg, robh+dt, mark.rutland, linux-i3c, devicetree,
	linux-arm-kernel, linux-kernel, linux-mediatek, srv_heupstream,
	leilk.liu, qii.wang, liguo.zhang, xinping.qian

This series are based on 5.2-rc1, we provide two patches to
support MediaTek I3C master controller.

Main changes compared to v2:
--modify the description of clock and interrupt in bindings
--use correct cells for I2C device in bindings

Main changes compared to v1:
--remove clock-div, let clock driver handle it
--let sample_cnt and step_cnt start from two

Qii Wang (2):
  dt-bindings: i3c: Document MediaTek I3C master bindings
  i3c: master: Add driver for MediaTek IP

 .../devicetree/bindings/i3c/mtk,i3c-master.txt     |   48 +
 drivers/i3c/master/Kconfig                         |   10 +
 drivers/i3c/master/Makefile                        |    1 +
 drivers/i3c/master/i3c-master-mtk.c                | 1239 ++++++++++++++++++++
 4 files changed, 1298 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i3c/mtk,i3c-master.txt
 create mode 100644 drivers/i3c/master/i3c-master-mtk.c

-- 
1.7.9.5

^ permalink raw reply

* Re: [PATCH v2 2/4] edac: Add support for Amazon's Annapurna Labs L1 EDAC
From: Jonathan Cameron @ 2019-07-09 12:30 UTC (permalink / raw)
  To: Hawa, Hanna
  Cc: robh+dt, mark.rutland, bp, mchehab, james.morse, davem, gregkh,
	linus.walleij, nicolas.ferre, paulmck, dwmw, benh, ronenk, talel,
	jonnyc, hanochu, devicetree, linux-kernel, linux-edac
In-Reply-To: <45e9ac35-9ffc-8f5f-cbdb-f85453227363@amazon.com>

On Tue, 9 Jul 2019 14:01:03 +0300
"Hawa, Hanna" <hhhawa@amazon.com> wrote:

> On 7/9/2019 12:32 PM, Jonathan Cameron wrote:
> >> Signed-off-by: Hanna Hawa<hhhawa@amazon.com>  
> > A quick drive by review as I was feeling curious.
> > 
> > Just a couple of trivial queries and observation on the fact it
> > might be useful to add a few devm managed functions to cut down
> > on edac driver boilerplate.
> > 
> > Thanks,
> > 
> > Jonathan
> >   
> >> +#define ARM_CA57_CPUMERRSR_VALID		GENMASK(31, 31)  
> > For a single bit it's common to use BIT(31) rather than GENMASK to make
> > it explicit.  
> 
> Will fix.
> 
> > 
> >   
> >> +	edac_dev->mod_name = dev_name(dev);  
> > I'd admit I'm not that familiar with edac, but seems odd that a
> > module name field would have the dev_name.  
> 
> Will fix when I got more inputs.
> 
> >   
> >> +	edac_device_free_ctl_info(edac_dev);  
> > More a passing observation than a suggestion for this driver, but if there was
> > ever a place where it looked like a couple of devm_ allocation functions would
> > be useful, this is it;)
> > 
> > edac_dev = devm_device_alloc_ctrl_info(dev, ...)
> > ...
> > devm_edac_device_add_device(dev, ...)  
> 
> I agree.
> I can implement the devm_* functions in separate patches as this is not 
> related to my patches (and not to delay this patches).
> 

Great.

Jonathan

> > 
> >   

^ permalink raw reply

* Re: [PATCH v2 1/2] firmware: imx: Add DSP IPC protocol interface
From: Oleksij Rempel @ 2019-07-09 12:02 UTC (permalink / raw)
  To: Daniel Baluta
  Cc: Daniel Baluta, shawnguo@kernel.org, robh+dt@kernel.org,
	s.hauer@pengutronix.de, S.j. Wang, festevam@gmail.com,
	dl-linux-imx, Aisheng Dong, Anson Huang,
	linux-kernel@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org, mark.rutland@arm.com,
	devicetree@vger.kernel.org
In-Reply-To: <CAEnQRZAGTdMpsnH-F_Xoaae0+nX8WTqYOrMJUjeQ6vhWvZ1y1Q@mail.gmail.com>

On Tue, Jul 09, 2019 at 08:48:20AM +0300, Daniel Baluta wrote:
> Hi Oleksij,
> 
> Any comments on this?

Looks good for me. All my suggestions was fixed.

Reviewed-by: <o.rempel@pengutronix.de>

> On Thu, Jun 27, 2019 at 11:14 AM Daniel Baluta <daniel.baluta@nxp.com> wrote:
> >
> > Some of i.MX8 processors (e.g i.MX8QM, i.MX8QXP) contain
> > the Tensilica HiFi4 DSP for advanced pre- and post-audio
> > processing.
> >
> > The communication between Host CPU and DSP firmware is
> > taking place using a shared memory area for message passing
> > and a dedicated Messaging Unit for notifications.
> >
> > DSP IPC protocol driver offers a doorbell interface using
> > imx-mailbox API.
> >
> > We use 4 MU channels (2 x TXDB, 2 x RXDB) to implement a
> > request-reply protocol.
> >
> > Connection 0 (txdb0, rxdb0):
> >         - Host writes messasge to shared memory [SHMEM]
> >         - Host sends a request [MU]
> >         - DSP handles request [SHMEM]
> >         - DSP sends reply [MU]
> >
> > Connection 1 (txdb1, rxdb1):
> >         - DSP writes a message to shared memory [SHMEM]
> >         - DSP sends a request [MU]
> >         - Host handles request [SHMEM]
> >         - Host sends reply [MU]
> >
> > The protocol driver will be used by a Host client to
> > communicate with the DSP. First client will be the i.MX8
> > part from Sound Open Firmware infrastructure.
> >
> > The protocol drivers offers the following interface:
> >
> > On Tx:
> >    - imx_dsp_ring_doorbell, will be called to notify the DSP
> >    that it needs to handle a request.
> >
> > On Rx:
> >    - clients need to provide two callbacks:
> >         .handle_reply
> >         .handle_request
> >   - the callbacks will be used by the protocol driver on
> >     notification arrival from DSP.
> >
> > Signed-off-by: Daniel Baluta <daniel.baluta@nxp.com>
> > ---
> >  drivers/firmware/imx/Kconfig     |  11 +++
> >  drivers/firmware/imx/Makefile    |   1 +
> >  drivers/firmware/imx/imx-dsp.c   | 142 +++++++++++++++++++++++++++++++
> >  include/linux/firmware/imx/dsp.h |  67 +++++++++++++++
> >  4 files changed, 221 insertions(+)
> >  create mode 100644 drivers/firmware/imx/imx-dsp.c
> >  create mode 100644 include/linux/firmware/imx/dsp.h
> >
> > diff --git a/drivers/firmware/imx/Kconfig b/drivers/firmware/imx/Kconfig
> > index 42b566f8903f..ddb241708c31 100644
> > --- a/drivers/firmware/imx/Kconfig
> > +++ b/drivers/firmware/imx/Kconfig
> > @@ -1,4 +1,15 @@
> >  # SPDX-License-Identifier: GPL-2.0-only
> > +config IMX_DSP
> > +       bool "IMX DSP Protocol driver"
> > +       depends on IMX_MBOX
> > +       help
> > +         This enables DSP IPC protocol between host CPU (Linux)
> > +         and the firmware running on DSP.
> > +         DSP exists on some i.MX8 processors (e.g i.MX8QM, i.MX8QXP).
> > +
> > +         It acts like a doorbell. Client might use shared memory to
> > +         exchange information with DSP side.
> > +
> >  config IMX_SCU
> >         bool "IMX SCU Protocol driver"
> >         depends on IMX_MBOX
> > diff --git a/drivers/firmware/imx/Makefile b/drivers/firmware/imx/Makefile
> > index 802c4ad8e8f9..08bc9ddfbdfb 100644
> > --- a/drivers/firmware/imx/Makefile
> > +++ b/drivers/firmware/imx/Makefile
> > @@ -1,3 +1,4 @@
> >  # SPDX-License-Identifier: GPL-2.0
> > +obj-$(CONFIG_IMX_DSP)          += imx-dsp.o
> >  obj-$(CONFIG_IMX_SCU)          += imx-scu.o misc.o imx-scu-irq.o
> >  obj-$(CONFIG_IMX_SCU_PD)       += scu-pd.o
> > diff --git a/drivers/firmware/imx/imx-dsp.c b/drivers/firmware/imx/imx-dsp.c
> > new file mode 100644
> > index 000000000000..c4d34a2fbff3
> > --- /dev/null
> > +++ b/drivers/firmware/imx/imx-dsp.c
> > @@ -0,0 +1,142 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Copyright 2019 NXP
> > + *  Author: Daniel Baluta <daniel.baluta@nxp.com>
> > + *
> > + * Implementation of the DSP IPC interface (host side)
> > + */
> > +
> > +#include <linux/firmware/imx/dsp.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mailbox_client.h>
> > +#include <linux/module.h>
> > +#include <linux/of_platform.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/slab.h>
> > +
> > +/*
> > + * imx_dsp_ring_doorbell - triggers an interrupt on the other side (DSP)
> > + *
> > + * @dsp: DSP IPC handle
> > + * @chan_idx: index of the channel where to trigger the interrupt
> > + *
> > + * Returns non-negative value for success, negative value for error
> > + */
> > +int imx_dsp_ring_doorbell(struct imx_dsp_ipc *ipc, unsigned int idx)
> > +{
> > +       int ret;
> > +       struct imx_dsp_chan *dsp_chan;
> > +
> > +       if (idx >= DSP_MU_CHAN_NUM)
> > +               return -EINVAL;
> > +
> > +       dsp_chan = &ipc->chans[idx];
> > +       ret = mbox_send_message(dsp_chan->ch, NULL);
> > +       if (ret < 0)
> > +               return ret;
> > +
> > +       return 0;
> > +}
> > +EXPORT_SYMBOL(imx_dsp_ring_doorbell);
> > +
> > +/*
> > + * imx_dsp_handle_rx - rx callback used by imx mailbox
> > + *
> > + * @c: mbox client
> > + * @msg: message received
> > + *
> > + * Users of DSP IPC will need to privde handle_reply and handle_request
> > + * callbacks.
> > + */
> > +static void imx_dsp_handle_rx(struct mbox_client *c, void *msg)
> > +{
> > +       struct imx_dsp_chan *chan = container_of(c, struct imx_dsp_chan, cl);
> > +
> > +       if (chan->idx == 0) {
> > +               chan->ipc->ops->handle_reply(chan->ipc);
> > +       } else {
> > +               chan->ipc->ops->handle_request(chan->ipc);
> > +               imx_dsp_ring_doorbell(chan->ipc, 1);
> > +       }
> > +}
> > +
> > +static int imx_dsp_probe(struct platform_device *pdev)
> > +{
> > +       struct device *dev = &pdev->dev;
> > +       struct imx_dsp_ipc *dsp_ipc;
> > +       struct imx_dsp_chan *dsp_chan;
> > +       struct mbox_client *cl;
> > +       char *chan_name;
> > +       int ret;
> > +       int i, j;
> > +
> > +       dsp_ipc = devm_kzalloc(dev, sizeof(*dsp_ipc), GFP_KERNEL);
> > +       if (!dsp_ipc)
> > +               return -ENOMEM;
> > +
> > +       for (i = 0; i < DSP_MU_CHAN_NUM; i++) {
> > +               if (i < 2)
> > +                       chan_name = kasprintf(GFP_KERNEL, "txdb%d", i);
> > +               else
> > +                       chan_name = kasprintf(GFP_KERNEL, "rxdb%d", i - 2);
> > +
> > +               if (!chan_name)
> > +                       return -ENOMEM;
> > +
> > +               dsp_chan = &dsp_ipc->chans[i];
> > +               cl = &dsp_chan->cl;
> > +               cl->dev = dev;
> > +               cl->tx_block = false;
> > +               cl->knows_txdone = true;
> > +               cl->rx_callback = imx_dsp_handle_rx;
> > +
> > +               dsp_chan->ipc = dsp_ipc;
> > +               dsp_chan->idx = i % 2;
> > +               dsp_chan->ch = mbox_request_channel_byname(cl, chan_name);
> > +               if (IS_ERR(dsp_chan->ch)) {
> > +                       ret = PTR_ERR(dsp_chan->ch);
> > +                       if (ret != -EPROBE_DEFER)
> > +                               dev_err(dev, "Failed to request mbox chan %s ret %d\n",
> > +                                       chan_name, ret);
> > +                       goto out;
> > +               }
> > +
> > +               dev_dbg(dev, "request mbox chan %s\n", chan_name);
> > +               /* chan_name is not used anymore by framework */
> > +               kfree(chan_name);
> > +       }
> > +
> > +       dsp_ipc->dev = dev;
> > +
> > +       dev_set_drvdata(dev, dsp_ipc);
> > +
> > +       dev_info(dev, "NXP i.MX DSP IPC initialized\n");
> > +
> > +       return devm_of_platform_populate(dev);
> > +out:
> > +       kfree(chan_name);
> > +       for (j = 0; j < i; j++) {
> > +               dsp_chan = &dsp_ipc->chans[j];
> > +               mbox_free_channel(dsp_chan->ch);
> > +       }
> > +
> > +       return ret;
> > +}
> > +
> > +static const struct of_device_id imx_dsp_match[] = {
> > +       { .compatible = "fsl,imx8qxp-dsp", },
> > +       { /* Sentinel */ }
> > +};
> > +
> > +static struct platform_driver imx_dsp_driver = {
> > +       .driver = {
> > +               .name = "imx-dsp",
> > +               .of_match_table = imx_dsp_match,
> > +       },
> > +       .probe = imx_dsp_probe,
> > +};
> > +builtin_platform_driver(imx_dsp_driver);
> > +
> > +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
> > +MODULE_DESCRIPTION("IMX DSP IPC protocol driver");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/include/linux/firmware/imx/dsp.h b/include/linux/firmware/imx/dsp.h
> > new file mode 100644
> > index 000000000000..7562099c9e46
> > --- /dev/null
> > +++ b/include/linux/firmware/imx/dsp.h
> > @@ -0,0 +1,67 @@
> > +/* SPDX-License-Identifier: GPL-2.0+ */
> > +/*
> > + * Copyright 2019 NXP
> > + *
> > + * Header file for the DSP IPC implementation
> > + */
> > +
> > +#ifndef _IMX_DSP_IPC_H
> > +#define _IMX_DSP_IPC_H
> > +
> > +#include <linux/device.h>
> > +#include <linux/types.h>
> > +#include <linux/mailbox_client.h>
> > +
> > +#define DSP_MU_CHAN_NUM                4
> > +
> > +struct imx_dsp_chan {
> > +       struct imx_dsp_ipc *ipc;
> > +       struct mbox_client cl;
> > +       struct mbox_chan *ch;
> > +       char *name;
> > +       int idx;
> > +};
> > +
> > +struct imx_dsp_ops {
> > +       void (*handle_reply)(struct imx_dsp_ipc *ipc);
> > +       void (*handle_request)(struct imx_dsp_ipc *ipc);
> > +};
> > +
> > +struct imx_dsp_ipc {
> > +       /* Host <-> DSP communication uses 2 txdb and 2 rxdb channels */
> > +       struct imx_dsp_chan chans[DSP_MU_CHAN_NUM];
> > +       struct device *dev;
> > +       struct imx_dsp_ops *ops;
> > +       void *private_data;
> > +};
> > +
> > +static inline void imx_dsp_set_data(struct imx_dsp_ipc *ipc, void *data)
> > +{
> > +       if (!ipc)
> > +               return;
> > +
> > +       ipc->private_data = data;
> > +}
> > +
> > +static inline void *imx_dsp_get_data(struct imx_dsp_ipc *ipc)
> > +{
> > +       if (!ipc)
> > +               return NULL;
> > +
> > +       return ipc->private_data;
> > +}
> > +
> > +#if IS_ENABLED(CONFIG_IMX_DSP)
> > +
> > +int imx_dsp_ring_doorbell(struct imx_dsp_ipc *dsp, unsigned int chan_idx);
> > +
> > +#else
> > +
> > +static inline int imx_dsp_ring_doorbell(struct imx_dsp_ipc *ipc,
> > +                                       unsigned int chan_idx)
> > +{
> > +       return -ENOTSUPP;
> > +}
> > +
> > +#endif
> > +#endif /* _IMX_DSP_IPC_H */
> > --
> > 2.17.1
> >
> 

-- 
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

^ permalink raw reply

* Re: [PATCH v2 1/2] media: dt-bindings: add bindings for Toshiba TC358746
From: Jacopo Mondi @ 2019-07-09 11:02 UTC (permalink / raw)
  To: Marco Felsch
  Cc: sakari.ailus, mchehab, robh+dt, devicetree, graphics, linux-media
In-Reply-To: <20190619152838.25079-2-m.felsch@pengutronix.de>

[-- Attachment #1: Type: text/plain, Size: 5205 bytes --]

Hi Marco,

On Wed, Jun 19, 2019 at 05:28:37PM +0200, Marco Felsch wrote:
> Add corresponding dt-bindings for the Toshiba tc358746 device and update
> the MAINTAINERS file too.
>
> Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>

With Rob's blessing, I'm glad I didn't take you off-road with my
comments on v1 :)

Reviewed-by: Jacopo Mondi <jacopo+renesas@jmondi.org>

Thanks
   j

> ---
> Hi Rob,
>
> I droped your reviewed-by tag since there where several changes.
>
> Changes
> v2:
>  - use the Documentation/devicetree/bindings/media/ti,omap3isp.txt
>    formatting scheme
>  - Adapt introducing paragraph according Jacopo's comments
>  - drop the clock-names property
>  - drop the clock-lanes (csi-2) property
>  - adapt the property descriptions accroding Jacopo's comments
>  - use port@0 as input and port@1 as output port instead of mapping
>    port@0 to parallel and port@1 to csi-2 interface
>  - Adapt port/endpoint description according Jacopo's and Sakari's
>    comments
>  - Adapt the example
>  - squash v1 patch-3 and patch-1
>  - set status to Maintained
>
>  .../bindings/media/i2c/toshiba,tc358746.txt   | 95 +++++++++++++++++++
>  MAINTAINERS                                   |  7 ++
>  2 files changed, 102 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.txt
>
> diff --git a/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.txt b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.txt
> new file mode 100644
> index 000000000000..e79b45ee050e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.txt
> @@ -0,0 +1,95 @@
> +Toshiba TC358746 Parallel to MIPI CSI2-TX or MIPI CSI2-RX to Parallel Bridge
> +============================================================================
> +
> +The Toshiba TC358746 converts a parallel into a MIPI CSI-2 stream. The direction
> +can be either parallel-in -> csi-out e.g. to connect a parallel camera to a
> +MIPI CSI-2 host or csi-in -> parallel-out e.g. to connect a parallel display to
> +a MIPI CSI-2 host. It's programmable trough I2C and SPI. The SPI interface is
> +only supported in parallel-in -> csi-out mode.
> +
> +Required properties
> +-------------------
> +
> +- compatible		: Shall be "toshiba,tc358746"
> +- reg			: Shall be <0x0e>
> +- clocks		: Phandle to the reference clock source
> +
> +Optional properties
> +-------------------
> +
> +- reset-gpios		: Phandle to the GPIO connected to the reset input pin
> +
> +Port nodes (required)
> +=====================
> +
> +The device node must contain two ports children nodes which should be grouped
> +within a 'ports' node. The first port describes the input connection, the second
> +one describes the output one. Each port shall contain one endpoint subnode that
> +connects to a remote device and specifies the bus type of the input and output
> +ports. Only one endpoint per port shall be present.
> +
> +More documentation on these bindings is available in [1].
> +
> +reg			: The interface:
> +			  0 - Input, either parallel or MIPI CSI-2
> +			  1 - Output, either parallel or MIPI CSI-2
> +
> +Endpoint nodes (required)
> +=========================
> +
> +Required properties
> +-------------------
> +
> +- bus-width		: Look at [1] for more information (Parallel endpoint)
> +- data-lanes		: Look at [1] for more information (CSI-2 endpoint)
> +- link-frequencies	: Array of frequencies the driver can choose to
> +			  full-fill the parallel timings calculation. Look at
> +			  [1] for more information (CSI-2 endpoint)
> +
> +Optional properties
> +-------------------
> +
> +- bus-type		: Look at [1] for more information
> +- clock-noncontinuous	: Look at [1] for more information (CSI-2 endpoint)
> +
> +[1] Documentation/devicetree/bindings/media/video-interfaces.txt
> +
> +Example
> +=======
> +
> +&i2c {
> +	csi-bridge@0e {
> +		reg = <0x0e>;
> +		compatible = "toshiba,tc358746";
> +		pinctrl-names = "default";
> +		clocks = <&clk_cam_ref>;
> +		reset-gpios = <&gpio3 2 GPIO_ACTIVE_LOW>;
> +
> +		ports {
> +			#address-cells = <1>;
> +			#size-cells = <0>;
> +
> +			/* Input */
> +			port@0 {
> +				reg = <0>;
> +
> +				tc358746_parallel_in: endpoint {
> +					bus-width = <8>;
> +					remote-endpoint = <&micron_parallel_out>;
> +				};
> +			};
> +
> +			/* Output */
> +			port@1 {
> +				reg = <1>;
> +
> +				tc358746_mipi2_out: endpoint {
> +					remote-endpoint = <&mipi_csi2_in>;
> +					data-lanes = <1 2>;
> +					clock-noncontinuous;
> +					link-frequencies = /bits/ 64 <216000000>;
> +				};
> +			};
> +		};
> +	};
> +};
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 57f496cff999..833b3c8fb301 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15955,6 +15955,13 @@ S:	Maintained
>  F:	drivers/media/i2c/tc358743*
>  F:	include/media/i2c/tc358743.h
>
> +TOSHIBA TC358746 DRIVER
> +M:	Marco Felsch <kernel@pengutronix.de>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +F:	drivers/media/i2c/tc358746*
> +F:	Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.txt
> +
>  TOSHIBA WMI HOTKEYS DRIVER
>  M:	Azael Avalos <coproscefalo@gmail.com>
>  L:	platform-driver-x86@vger.kernel.org
> --
> 2.20.1
>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply

* Re: [PATCH v2 2/4] edac: Add support for Amazon's Annapurna Labs L1 EDAC
From: Hawa, Hanna @ 2019-07-09 11:01 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: robh+dt, mark.rutland, bp, mchehab, james.morse, davem, gregkh,
	linus.walleij, nicolas.ferre, paulmck, dwmw, benh, ronenk, talel,
	jonnyc, hanochu, devicetree, linux-kernel, linux-edac
In-Reply-To: <20190709173229.0000135f@huawei.com>



On 7/9/2019 12:32 PM, Jonathan Cameron wrote:
>> Signed-off-by: Hanna Hawa<hhhawa@amazon.com>
> A quick drive by review as I was feeling curious.
> 
> Just a couple of trivial queries and observation on the fact it
> might be useful to add a few devm managed functions to cut down
> on edac driver boilerplate.
> 
> Thanks,
> 
> Jonathan
> 
>> +#define ARM_CA57_CPUMERRSR_VALID		GENMASK(31, 31)
> For a single bit it's common to use BIT(31) rather than GENMASK to make
> it explicit.

Will fix.

> 
> 
>> +	edac_dev->mod_name = dev_name(dev);
> I'd admit I'm not that familiar with edac, but seems odd that a
> module name field would have the dev_name.

Will fix when I got more inputs.

> 
>> +	edac_device_free_ctl_info(edac_dev);
> More a passing observation than a suggestion for this driver, but if there was
> ever a place where it looked like a couple of devm_ allocation functions would
> be useful, this is it;)
> 
> edac_dev = devm_device_alloc_ctrl_info(dev, ...)
> ...
> devm_edac_device_add_device(dev, ...)

I agree.
I can implement the devm_* functions in separate patches as this is not 
related to my patches (and not to delay this patches).

> 
> 

^ permalink raw reply

* Re: [RFC PATCH V2 4/4] platform: mtk-isp: Add Mediatek FD driver
From: Enrico Weigelt, metux IT consult @ 2019-07-09 10:56 UTC (permalink / raw)
  To: Jerry-ch Chen, hans.verkuil, laurent.pinchart+renesas, tfiga,
	matthias.bgg, mchehab
  Cc: devicetree, Sean.Cheng, Rynn.Wu, srv_heupstream, po-yang.huang,
	suleiman, sj.huang, jungo.lin, shik, yuzhao, linux-mediatek,
	zwisler, christie.yu, frederic.chen, linux-arm-kernel,
	linux-media
In-Reply-To: <1562661672-22439-5-git-send-email-Jerry-Ch.chen@mediatek.com>

On 09.07.19 10:41, Jerry-ch Chen wrote:

Hi,


> diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd.h b/drivers/media/platform/mtk-isp/fd/mtk_fd.h
> new file mode 100644
> index 0000000..289999b
> --- /dev/null
> +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd.h
> @@ -0,0 +1,157 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +//
> +// Copyright (c) 2018 MediaTek Inc.
> +
> +#ifndef __MTK_FD_HW_H__
> +#define __MTK_FD_HW_H__
> +
> +#include <linux/io.h>
> +#include <linux/types.h>
> +#include <linux/platform_device.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#define MTK_FD_OUTPUT_MIN_WIDTH			26U
> +#define MTK_FD_OUTPUT_MIN_HEIGHT		26U
> +#define MTK_FD_OUTPUT_MAX_WIDTH			640U
> +#define MTK_FD_OUTPUT_MAX_HEIGHT		480U
> +
> +/* Control the user defined image widths and heights
> + * to be scaled and performed face detection in FD HW.
> + * MTK FD support up to 14 user defined image sizes to perform face detection.
> + */
> +#define V4L2_CID_MTK_FD_SCALE_IMG_WIDTH		(V4L2_CID_USER_MTK_FD_BASE + 1)
> +#define V4L2_CID_MTK_FD_SCALE_IMG_HEIGHT	(V4L2_CID_USER_MTK_FD_BASE + 2)

I've got a *really* bad feeling about introducing chip specific
uapi stuff. (by the way: uapi stuff belongs into include/uapi/...)

Maybe you could tell us what that's *really* about, so we can find some
standard / chip-independent api for these things. That's one of the
major point of the kernel: hardware abstraction.

> +#define ENABLE_FD				0x111
> +#define FD_HW_ENABLE				0x4
> +#define FD_INT_EN				0x15c
> +#define FD_INT					0x168
> +#define FD_RESULT				0x178
> +#define FD_IRQ_MASK				0x001
> +
> +#define RS_MAX_BUF_SIZE				2288788
> +#define FD_MAX_SPEEDUP				7
> +#define FD_MAX_POSE_VAL				0xfffffffffffffff
> +#define FD_DEF_POSE_VAL				0x3ff
> +#define MAX_FD_SEL_NUM				1026

If that file is supposed to be included by anything beyond the driver
itself, we need proper prefixing. (same for anything else in here)

> diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
> index 3dcfc61..eae876e 100644
> --- a/include/uapi/linux/v4l2-controls.h
> +++ b/include/uapi/linux/v4l2-controls.h
> @@ -192,6 +192,10 @@ enum v4l2_colorfx {
>   * We reserve 16 controls for this driver. */
>  #define V4L2_CID_USER_IMX_BASE			(V4L2_CID_USER_BASE + 0x10b0)
>  
> +/* The base for the mediatek FD driver controls */
> +/* We reserve 16 controls for this driver. */
> +#define V4L2_CID_USER_MTK_FD_BASE		(V4L2_CID_USER_BASE + 0x10d0)

Why only the base, but not the actual IDs in uapi ?


--mtx

-- 
Enrico Weigelt, metux IT consult
Free software and Linux embedded engineering
info@metux.net -- +49-151-27565287

^ permalink raw reply

* RE: [PATCH V2 2/2] dt-bindings: Document the DesignWare IP reset bindings
From: Luis de Oliveira @ 2019-07-09 10:33 UTC (permalink / raw)
  To: Rob Herring, Luis Oliveira
  Cc: p.zabel@pengutronix.de, mark.rutland@arm.com,
	linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
	Joao.Pinto@synopsys.com, Gustavo Pimentel
In-Reply-To: <20190709015220.GA18239@bogus>

Hi Rob,

Thank you for the comments,

From: Rob Herring <robh@kernel.org>
Date: Tue, Jul 09, 2019 at 02:52:20

> On Thu, Jun 06, 2019 at 05:36:28PM +0200, Luis Oliveira wrote:
> > This adds documentation of device tree bindings for the
> > DesignWare IP reset controller.
> > 
> > Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
> > Signed-off-by: Luis Oliveira <luis.oliveira@synopsys.com>
> > ---
> > Changelog
> > - Add active low configuration example
> > - Fix compatible string in the active high example
> > 
> >  .../devicetree/bindings/reset/snps,dw-reset.txt    | 30 ++++++++++++++++++++++
> >  1 file changed, 30 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/reset/snps,dw-reset.txt
> > 
> > diff --git a/Documentation/devicetree/bindings/reset/snps,dw-reset.txt b/Documentation/devicetree/bindings/reset/snps,dw-reset.txt
> > new file mode 100644
> > index 0000000..85f3301
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/reset/snps,dw-reset.txt
> > @@ -0,0 +1,30 @@
> > +Synopsys DesignWare Reset controller
> > +=======================================
> > +
> > +Please also refer to reset.txt in this directory for common reset
> > +controller binding usage.
> > +
> > +Required properties:
> > +
> > +- compatible: should be one of the following.
> > +	"snps,dw-high-reset" - for active high configuration
> > +	"snps,dw-low-reset" - for active low configuration
> 
> This is really a standalone block?
> 
> Are there versions of IP?
> 

We use this block because is is very simple. The Verilog is autogenerated 
after an simple input configuration (APB config, reset pin number, active 
high/low, etc) so it does not need versioning.
We use it in almost all our testchips and prototyping, and it is a 
standalone block.

> > +
> > +- reg: physical base address of the controller and length of memory mapped
> > +	region.
> > +
> > +- #reset-cells: must be 1.
> > +
> > +example:
> > +
> > +	dw_rst_1: reset-controller@0000 {
> > +		  compatible = "snps,dw-high-reset";
> > +	 	  reg = <0x0000 0x4>;
> > +		  #reset-cells = <1>;
> > +	};
> > +
> > +	dw_rst_2: reset-controller@1000 {i
> > +		  compatible = "snps,dw-low-reset";
> > +		  reg = <0x1000 0x8>;
> > +		  #reset-cells = <1>;
> > +	};
> > -- 
> > 2.7.4
> > 

Thank you Rob,

Luis

^ permalink raw reply

* RE: [PATCH 5.2 v2 2/2] dt-binding: edac: add NPCM ECC documentation
From: George Hung (洪忠敬) @ 2019-07-09  9:48 UTC (permalink / raw)
  To: Rob Herring, George Hung
  Cc: Mark Rutland, Linus Walleij, Tali Perry, paulmck@linux.ibm.com,
	wak@google.com, benjaminfair@google.com, openbmc@lists.ozlabs.org,
	tomer.maimon@nuvoton.com, devicetree@vger.kernel.org,
	Borislav Petkov, Avi.Fishman@nuvoton.com, Jonathan Cameron,
	Mauro Carvalho Chehab, linux-edac, Patrick Venture, Nicolas Ferre,
	linux-kernel
In-Reply-To: <20190709014058.GA30269@bogus>

Hi Rob,


> -----Original Message-----
> From: openbmc
> [mailto:openbmc-bounces+george.hung=quantatw.com@lists.ozlabs.org] On
> Behalf Of Rob Herring
> Sent: Tuesday, July 09, 2019 9:41 AM
> To: George Hung
> Cc: Mark Rutland; Linus Walleij; Tali Perry; paulmck@linux.ibm.com;
> wak@google.com; benjaminfair@google.com; openbmc@lists.ozlabs.org;
> tomer.maimon@nuvoton.com; devicetree@vger.kernel.org; Borislav Petkov;
> Avi.Fishman@nuvoton.com; Jonathan Cameron; Mauro Carvalho Chehab;
> linux-edac; Patrick Venture; Nicolas Ferre; linux-kernel; James Morse; Greg
> Kroah-Hartman; davem@davemloft.net
> Subject: Re: [PATCH 5.2 v2 2/2] dt-binding: edac: add NPCM ECC
> documentation
> 
> On Wed, Jun 05, 2019 at 10:12:53PM +0800, George Hung wrote:
> > Add device tree documentation for Nuvoton BMC ECC
> >
> > Signed-off-by: George Hung <ghung.quanta@gmail.com>
> > ---
> >  .../bindings/edac/npcm7xx-sdram-edac.txt        | 17
> +++++++++++++++++
> >  1 file changed, 17 insertions(+)
> >  create mode 100644
> > Documentation/devicetree/bindings/edac/npcm7xx-sdram-edac.txt
> >
> > diff --git
> > a/Documentation/devicetree/bindings/edac/npcm7xx-sdram-edac.txt
> > b/Documentation/devicetree/bindings/edac/npcm7xx-sdram-edac.txt
> > new file mode 100644
> > index 000000000000..dd4dac59a5bd
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/edac/npcm7xx-sdram-edac.txt
> > @@ -0,0 +1,17 @@
> > +Nuvoton NPCM7xx SoC EDAC device driver
> > +
> > +The Nuvoton NPCM7xx SoC supports DDR4 memory with/without ECC and
> the
> > +driver uses the EDAC framework to implement the ECC detection and
> corrtection.
> > +
> > +Required properties:
> > +- compatible:	should be "nuvoton,npcm7xx-sdram-edac"
> 
> Is this for the whole SDRAM controller or just ECC related registers?
> In the former case, the naming should just reflect the block name and not a
> Linux term.

Sorry for confused naming, the address space is for the whole memory controller registers indeed,
but the driver only uses the ECC related registers.
Should I change the name to "nuvoton,npcm7xx-edac" ?

> 
> > +- reg:		Memory controller register set should be <0xf0824000
> 0x1000>
> > +- interrupts:	should be MC interrupt #25
> > +
> > +Example:
> > +
> > +	mc: memory-controller@f0824000 {
> > +		compatible = "nuvoton,npcm7xx-sdram-edac";
> > +		reg = <0xf0824000 0x1000>;
> > +		interrupts = <0 25 4>;
> > +	};
> > --
> > 2.21.0
> >

^ permalink raw reply

* Re: [PATCH v2 05/10] net: hisilicon: HI13X1_GMAX need dreq reset at first
From: Sergei Shtylyov @ 2019-07-09  9:35 UTC (permalink / raw)
  To: Jiangfeng Xiao, davem, robh+dt, yisen.zhuang, salil.mehta,
	mark.rutland, dingtianhong
  Cc: netdev, devicetree, linux-kernel, leeyou.li, nixiaoming,
	jianping.liu, xiekunxun
In-Reply-To: <1562643071-46811-6-git-send-email-xiaojiangfeng@huawei.com>

Hello!

On 09.07.2019 6:31, Jiangfeng Xiao wrote:

> HI13X1_GMAC delete request for soft reset at first,
> otherwise, the subsequent initialization will not
> take effect.
> 
> Signed-off-by: Jiangfeng Xiao <xiaojiangfeng@huawei.com>
> ---
>   drivers/net/ethernet/hisilicon/hip04_eth.c | 24 ++++++++++++++++++++++++
>   1 file changed, 24 insertions(+)
> 
> diff --git a/drivers/net/ethernet/hisilicon/hip04_eth.c b/drivers/net/ethernet/hisilicon/hip04_eth.c
> index fe61b01..19d8cfd 100644
> --- a/drivers/net/ethernet/hisilicon/hip04_eth.c
> +++ b/drivers/net/ethernet/hisilicon/hip04_eth.c
[...]
> @@ -853,6 +867,15 @@ static int hip04_mac_probe(struct platform_device *pdev)
>   		goto init_fail;
>   	}
>   
> +#if defined(CONFIG_HI13X1_GMAC)
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +	priv->sysctrl_base = devm_ioremap_resource(d, res);

    There's devm_platform_ioremap_resource() now.

> +	if (IS_ERR(priv->sysctrl_base)) {
> +		ret = PTR_ERR(priv->sysctrl_base);
> +		goto init_fail;
> +	}
> +#endif
> +
>   	ret = of_parse_phandle_with_fixed_args(node, "port-handle", 2, 0, &arg);
>   	if (ret < 0) {
>   		dev_warn(d, "no port-handle\n");
[...]

MBR, Sergei

^ permalink raw reply

* Re: [PATCH v2 2/4] edac: Add support for Amazon's Annapurna Labs L1 EDAC
From: Jonathan Cameron @ 2019-07-09  9:32 UTC (permalink / raw)
  To: Hanna Hawa
  Cc: robh+dt, mark.rutland, bp, mchehab, james.morse, davem, gregkh,
	linus.walleij, nicolas.ferre, paulmck, dwmw, benh, ronenk, talel,
	jonnyc, hanochu, devicetree, linux-kernel, linux-edac
In-Reply-To: <1562500658-14717-3-git-send-email-hhhawa@amazon.com>

On Sun, 7 Jul 2019 14:57:36 +0300
Hanna Hawa <hhhawa@amazon.com> wrote:

> Adds support for Amazon's Annapurna Labs L1 EDAC driver to detect and
> report L1 errors.
> 
> Signed-off-by: Hanna Hawa <hhhawa@amazon.com>
A quick drive by review as I was feeling curious.

Just a couple of trivial queries and observation on the fact it
might be useful to add a few devm managed functions to cut down
on edac driver boilerplate.

Thanks,

Jonathan

> ---
>  MAINTAINERS               |   6 ++
>  drivers/edac/Kconfig      |   8 +++
>  drivers/edac/Makefile     |   1 +
>  drivers/edac/al_l1_edac.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 169 insertions(+)
>  create mode 100644 drivers/edac/al_l1_edac.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 77eae44..fd29ea6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -743,6 +743,12 @@ F:	drivers/tty/serial/altera_jtaguart.c
>  F:	include/linux/altera_uart.h
>  F:	include/linux/altera_jtaguart.h
>  
> +AMAZON ANNAPURNA LABS L1 EDAC
> +M:	Hanna Hawa <hhhawa@amazon.com>
> +S:	Maintained
> +F:	drivers/edac/al_l1_edac.c
> +F:	Documentation/devicetree/bindings/edac/amazon,al-l1-edac.txt
> +
>  AMAZON ANNAPURNA LABS THERMAL MMIO DRIVER
>  M:	Talel Shenhar <talel@amazon.com>
>  S:	Maintained
> diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig
> index 200c04c..58b92bc 100644
> --- a/drivers/edac/Kconfig
> +++ b/drivers/edac/Kconfig
> @@ -74,6 +74,14 @@ config EDAC_GHES
>  
>  	  In doubt, say 'Y'.
>  
> +config EDAC_AL_L1
> +	bool "Amazon's Annapurna Labs L1 EDAC"
> +	depends on ARCH_ALPINE
> +	help
> +	  Support for L1 error detection and correction
> +	  for Amazon's Annapurna Labs SoCs.
> +	  This driver detects errors of L1 caches.
> +
>  config EDAC_AMD64
>  	tristate "AMD64 (Opteron, Athlon64)"
>  	depends on AMD_NB && EDAC_DECODE_MCE
> diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile
> index 165ca65e..caa2dc9 100644
> --- a/drivers/edac/Makefile
> +++ b/drivers/edac/Makefile
> @@ -22,6 +22,7 @@ obj-$(CONFIG_EDAC_GHES)			+= ghes_edac.o
>  edac_mce_amd-y				:= mce_amd.o
>  obj-$(CONFIG_EDAC_DECODE_MCE)		+= edac_mce_amd.o
>  
> +obj-$(CONFIG_EDAC_AL_L1)		+= al_l1_edac.o
>  obj-$(CONFIG_EDAC_AMD76X)		+= amd76x_edac.o
>  obj-$(CONFIG_EDAC_CPC925)		+= cpc925_edac.o
>  obj-$(CONFIG_EDAC_I5000)		+= i5000_edac.o
> diff --git a/drivers/edac/al_l1_edac.c b/drivers/edac/al_l1_edac.c
> new file mode 100644
> index 0000000..f51a6c3
> --- /dev/null
> +++ b/drivers/edac/al_l1_edac.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
> + */
> +
> +#include <linux/bitfield.h>
> +
> +#include "edac_device.h"
> +#include "edac_module.h"
> +
> +/* Same bit assignments of CPUMERRSR_EL1 in ARM CA57/CA72 */
> +#define ARM_CA57_CPUMERRSR_EL1			sys_reg(3, 1, 15, 2, 2)
> +#define ARM_CA57_CPUMERRSR_RAM_ID		GENMASK(30, 24)
> +#define  ARM_CA57_L1_I_TAG_RAM			0x00
> +#define  ARM_CA57_L1_I_DATA_RAM			0x01
> +#define  ARM_CA57_L1_D_TAG_RAM			0x08
> +#define  ARM_CA57_L1_D_DATA_RAM			0x09
> +#define  ARM_CA57_L2_TLB_RAM			0x18
> +#define ARM_CA57_CPUMERRSR_VALID		GENMASK(31, 31)

For a single bit it's common to use BIT(31) rather than GENMASK to make
it explicit.


> +#define ARM_CA57_CPUMERRSR_REPEAT		GENMASK(39, 32)
> +#define ARM_CA57_CPUMERRSR_OTHER		GENMASK(47, 40)
> +#define ARM_CA57_CPUMERRSR_FATAL		GENMASK(63, 63)
> +
> +#define AL_L1_EDAC_MSG_MAX			256
> +
> +static void al_l1_edac_cpumerrsr(void *arg)
> +{
> +	struct edac_device_ctl_info *edac_dev = arg;
> +	int cpu, i;
> +	u32 ramid, repeat, other, fatal;
> +	u64 val = read_sysreg_s(ARM_CA57_CPUMERRSR_EL1);
> +	char msg[AL_L1_EDAC_MSG_MAX];
> +	int space, count;
> +	char *p;
> +
> +	if (!(FIELD_GET(ARM_CA57_CPUMERRSR_VALID, val)))
> +		return;
> +
> +	cpu = smp_processor_id();
> +	ramid = FIELD_GET(ARM_CA57_CPUMERRSR_RAM_ID, val);
> +	repeat = FIELD_GET(ARM_CA57_CPUMERRSR_REPEAT, val);
> +	other = FIELD_GET(ARM_CA57_CPUMERRSR_OTHER, val);
> +	fatal = FIELD_GET(ARM_CA57_CPUMERRSR_FATAL, val);
> +
> +	space = sizeof(msg);
> +	p = msg;
> +	count = snprintf(p, space, "CPU%d L1 %serror detected", cpu,
> +			 (fatal) ? "Fatal " : "");
> +	p += count;
> +	space -= count;
> +
> +	switch (ramid) {
> +	case ARM_CA57_L1_I_TAG_RAM:
> +		count = snprintf(p, space, " RAMID='L1-I Tag RAM'");
> +		break;
> +	case ARM_CA57_L1_I_DATA_RAM:
> +		count = snprintf(p, space, " RAMID='L1-I Data RAM'");
> +		break;
> +	case ARM_CA57_L1_D_TAG_RAM:
> +		count = snprintf(p, space, " RAMID='L1-D Tag RAM'");
> +		break;
> +	case ARM_CA57_L1_D_DATA_RAM:
> +		count = snprintf(p, space, " RAMID='L1-D Data RAM'");
> +		break;
> +	case ARM_CA57_L2_TLB_RAM:
> +		count = snprintf(p, space, " RAMID='L2 TLB RAM'");
> +		break;
> +	default:
> +		count = snprintf(p, space, " RAMID='unknown'");
> +		break;
> +	}
> +
> +	p += count;
> +	space -= count;
> +	count = snprintf(p, space,
> +			 " repeat=%d, other=%d (CPUMERRSR_EL1=0x%llx)",
> +			 repeat, other, val);
> +
> +	for (i = 0; i < repeat; i++) {
> +		if (fatal)
> +			edac_device_handle_ue(edac_dev, 0, 0, msg);
> +		else
> +			edac_device_handle_ce(edac_dev, 0, 0, msg);
> +	}
> +
> +	write_sysreg_s(0, ARM_CA57_CPUMERRSR_EL1);
> +}
> +
> +static void al_l1_edac_check(struct edac_device_ctl_info *edac_dev)
> +{
> +	on_each_cpu(al_l1_edac_cpumerrsr, edac_dev, 1);
> +}
> +
> +static int al_l1_edac_probe(struct platform_device *pdev)
> +{
> +	struct edac_device_ctl_info *edac_dev;
> +	struct device *dev = &pdev->dev;
> +	int ret;
> +
> +	edac_dev = edac_device_alloc_ctl_info(0, (char *)dev_name(dev), 1, "L",
> +					      1, 1, NULL, 0,
> +					      edac_device_alloc_index());
> +	if (IS_ERR(edac_dev))
> +		return -ENOMEM;
> +
> +	edac_dev->edac_check = al_l1_edac_check;
> +	edac_dev->dev = dev;
> +	edac_dev->mod_name = dev_name(dev);

I'd admit I'm not that familiar with edac, but seems odd that a
module name field would have the dev_name. 

> +	edac_dev->dev_name = dev_name(dev);
> +	edac_dev->ctl_name = dev_name(dev);
> +	platform_set_drvdata(pdev, edac_dev);
> +
> +	ret = edac_device_add_device(edac_dev);
> +	if (ret) {
> +		dev_err(dev, "Failed to add L1 edac device\n");
> +		goto err;
> +	}
> +
> +	return 0;
> +err:
> +	edac_device_free_ctl_info(edac_dev);
> +
> +	return ret;
> +}
> +
> +static int al_l1_edac_remove(struct platform_device *pdev)
> +{
> +	struct edac_device_ctl_info *edac_dev = platform_get_drvdata(pdev);
> +
> +	edac_device_del_device(edac_dev->dev);
> +	edac_device_free_ctl_info(edac_dev);

More a passing observation than a suggestion for this driver, but if there was
ever a place where it looked like a couple of devm_ allocation functions would
be useful, this is it ;)

edac_dev = devm_device_alloc_ctrl_info(dev, ...)
...
devm_edac_device_add_device(dev, ...)


> +
> +	return 0;
> +}
> +
> +static const struct of_device_id al_l1_edac_of_match[] = {
> +	{ .compatible = "amazon,al-l1-edac" },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, al_l1_edac_of_match);
> +
> +static struct platform_driver al_l1_edac_driver = {
> +	.probe = al_l1_edac_probe,
> +	.remove = al_l1_edac_remove,
> +	.driver = {
> +		.name = "al_l1_edac",
> +		.of_match_table = al_l1_edac_of_match,
> +	},
> +};
> +module_platform_driver(al_l1_edac_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Hanna Hawa <hhhawa@amazon.com>");
> +MODULE_DESCRIPTION("Amazon's Annapurna Lab's L1 EDAC Driver");

^ permalink raw reply

* Re: [PATCH v6 1/6] dt-bindings: usb: musb: Add support for MediaTek musb controller
From: Min Guo @ 2019-07-09  9:10 UTC (permalink / raw)
  To: Rob Herring
  Cc: Bin Liu, Greg Kroah-Hartman, Mark Rutland, Matthias Brugger,
	Alan Stern, chunfeng.yun, linux-usb, devicetree, linux-kernel,
	linux-arm-kernel, linux-mediatek, tony, hdegoede
In-Reply-To: <20190708223035.GA7005@bogus>

Hi Rob,
On Mon, 2019-07-08 at 16:30 -0600, Rob Herring wrote:
> On Tue, Jun 04, 2019 at 07:39:14PM +0800, min.guo@mediatek.com wrote:
> > From: Min Guo <min.guo@mediatek.com>
> > 
> > This adds support for MediaTek musb controller in
> > host, peripheral and otg mode.
> > 
> > Signed-off-by: Min Guo <min.guo@mediatek.com>
> > ---
> > changes in v6:
> > 1. Modify usb connector child node
> > 
> > changes in v5:
> > suggested by Rob:
> > 1. Modify compatible as 
> > - compatible : should be one of:
> >                "mediatek,mt-2701"
> 
> No, should be: mediatek,mt2701-musb
OK.

> >                ...
> >                followed by "mediatek,mtk-musb"
> > 2. Add usb connector child node
> > 
> > changes in v4:
> > suggested by Sergei:
> > 1. String alignment
> > 
> > changes in v3:
> > 1. no changes
> > 
> > changes in v2:
> > suggested by Bin:
> > 1. Modify DRC to DRD
> > suggested by Rob:
> > 2. Drop the "<soc-model>-musb" in compatible
> > 3. Remove phy-names
> > 4. Add space after comma in clock-names
> > ---
> >  .../devicetree/bindings/usb/mediatek,musb.txt      | 55 ++++++++++++++++++++++
> >  1 file changed, 55 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/usb/mediatek,musb.txt
> > 
> > diff --git a/Documentation/devicetree/bindings/usb/mediatek,musb.txt b/Documentation/devicetree/bindings/usb/mediatek,musb.txt
> > new file mode 100644
> > index 0000000..7434299
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/usb/mediatek,musb.txt
> > @@ -0,0 +1,55 @@
> > +MediaTek musb DRD/OTG controller
> > +-------------------------------------------
> > +
> > +Required properties:
> > + - compatible      : should be one of:
> > +                     "mediatek,mt-2701"
> > +                     ...
> > +                     followed by "mediatek,mtk-musb"
> > + - reg             : specifies physical base address and size of
> > +                     the registers
> > + - interrupts      : interrupt used by musb controller
> > + - interrupt-names : must be "mc"
> > + - phys            : PHY specifier for the OTG phy
> > + - dr_mode         : should be one of "host", "peripheral" or "otg",
> > +                     refer to usb/generic.txt
> > + - clocks          : a list of phandle + clock-specifier pairs, one for
> > +                     each entry in clock-names
> > + - clock-names     : must contain "main", "mcu", "univpll"
> > +                     for clocks of controller
> > +
> > +Optional properties:
> > + - power-domains   : a phandle to USB power domain node to control USB's
> > +                     MTCMOS
> > +
> > +Required child nodes:
> > + usb connector node as defined in bindings/connector/usb-connector.txt
> > +Optional properties:
> > + - id-gpios        : input GPIO for USB ID pin.
> > + - vbus-gpios      : input GPIO for USB VBUS pin.
> > + - vbus-supply     : reference to the VBUS regulator, needed when supports
> > +                     dual-role mode
> > +
> > +Example:
> > +
> > +usb2: usb@11200000 {
> > +	compatible = "mediatek,mt2701-musb",
> > +		     "mediatek,mtk-musb";
> > +	reg = <0 0x11200000 0 0x1000>;
> > +	interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_LOW>;
> > +	interrupt-names = "mc";
> > +	phys = <&u2port2 PHY_TYPE_USB2>;
> > +	dr_mode = "otg";
> > +	clocks = <&pericfg CLK_PERI_USB0>,
> > +		 <&pericfg CLK_PERI_USB0_MCU>,
> > +		 <&pericfg CLK_PERI_USB_SLV>;
> > +	clock-names = "main","mcu","univpll";
> > +	power-domains = <&scpsys MT2701_POWER_DOMAIN_IFR_MSC>;
> > +	connector{
> > +		compatible = "linux,typeb-conn-gpio", "usb-b-connector";
> 
> linux,typeb-conn-gpio is not an accepted compatible string.
The compatible depends on connector driver, currently under review.
https://patchwork.kernel.org/patch/10986245

> > +		label = "micro-USB";
> > +		type = "micro";
> > +		id-gpios = <&pio 44 GPIO_ACTIVE_HIGH>;
> > +		vbus-supply = <&usb_vbus>;
> > +	};
> > +};
> > -- 
> > 1.9.1
> > 

Regards,
Min.

^ permalink raw reply

* Re: [PATCH v2 7/7] arm64: dts: allwinner: a64: enable ANX6345 bridge on Teres-I
From: Icenowy Zheng @ 2019-07-09  8:58 UTC (permalink / raw)
  To: Maxime Ripard, Vasily Khoruzhick
  Cc: Mark Rutland, devicetree, Sean Paul, David Airlie,
	Greg Kroah-Hartman, linux-kernel, dri-devel, Andrzej Hajda,
	Chen-Yu Tsai, Rob Herring, Torsten Duwe, Laurent Pinchart,
	Daniel Vetter, Harald Geyer, Thierry Reding, Thomas Gleixner,
	arm-linux
In-Reply-To: <20190709085532.cdqv7whuesrjs64c@flea>



于 2019年7月9日 GMT+08:00 下午4:55:32, Maxime Ripard <maxime.ripard@bootlin.com> 写到:
>On Mon, Jul 08, 2019 at 05:49:21PM -0700, Vasily Khoruzhick wrote:
>> > > Maybe instead of edp-connector one would introduce integrator's
>specific
>> > > connector, for example with compatible
>"olimex,teres-edp-connector"
>> > > which should follow edp abstract connector rules? This will be at
>least
>> > > consistent with below presentation[1] - eDP requirements depends
>on
>> > > integrator. Then if olimex has standard way of dealing with
>panels
>> > > present in olimex/teres platforms the driver would then create
>> > > drm_panel/drm_connector/drm_bridge(?) according to these rules, I
>guess.
>> > > Anyway it still looks fishy for me :), maybe because I am not
>> > > familiarized with details of these platforms.
>> >
>> > That makes sense yes
>>
>> Actually, it makes no sense at all. Current implementation for
>anx6345
>> driver works fine as is with any panel specified assuming panel
>delays
>> are long enough for connected panel. It just doesn't use panel
>timings
>> from the driver. Creating a platform driver for connector itself
>looks
>> redundant since it can't be reused, it doesn't describe actual
>> hardware and it's just defeats purpose of DT by introducing
>> board-specific code.
>
>I'm not sure where you got the idea that the purpose of DT is to not
>have any board-specific code.
>
>It's perfectly fine to have some, that's even why there's a compatible
>assigned to each and every board.
>
>What the DT is about is allowing us to have a generic behaviour that
>we can detect: we can have a given behaviour for a given board, and a
>separate one for another one, and this will be evaluated at runtime.
>
>This is *exactly* what this is about: we can have a compatible that
>sets a given, more specific, behaviour (olimex,teres-edp-connector)
>while saying that this is compatible with the generic behaviour
>(edp-connector). That way, any OS will know what quirk to apply if
>needed, and if not that it can use the generic behaviour.
>
>And we could create a generic driver, for the generic behaviour if
>needed.
>
>> There's another issue: if we introduce edp-connector we'll have to
>> specify power up delays somewhere (in dts? or in platform driver?),
>so
>> edp-connector doesn't really solve the issue of multiple panels with
>> same motherboard.
>
>And that's what that compatible is about :)

Maybe we can introduce a connector w/o any driver just like hdmi-connector?

>
>> I'd say DT overlays should be preferred solution here, not another
>> connector binding.
>
>Overlays are a way to apply a device tree dynamically. It's orthogonal
>to the binding.
>
>Maxime
>
>--
>Maxime Ripard, Bootlin
>Embedded Linux and Kernel engineering
>https://bootlin.com

-- 
使用 K-9 Mail 发送自我的Android设备。

^ permalink raw reply

* Re: [PATCH v2 7/7] arm64: dts: allwinner: a64: enable ANX6345 bridge on Teres-I
From: Maxime Ripard @ 2019-07-09  8:55 UTC (permalink / raw)
  To: Vasily Khoruzhick
  Cc: Mark Rutland, devicetree, Sean Paul, David Airlie,
	Greg Kroah-Hartman, linux-kernel, dri-devel, Chen-Yu Tsai,
	Rob Herring, Torsten Duwe, Laurent Pinchart, Harald Geyer,
	Thierry Reding, Thomas Gleixner, arm-linux, Icenowy Zheng
In-Reply-To: <CA+E=qVdsYV2Bxk245=Myq=otd7-7WHzUnSJN8_1dciAzvSOG8g@mail.gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 2554 bytes --]

On Mon, Jul 08, 2019 at 05:49:21PM -0700, Vasily Khoruzhick wrote:
> > > Maybe instead of edp-connector one would introduce integrator's specific
> > > connector, for example with compatible "olimex,teres-edp-connector"
> > > which should follow edp abstract connector rules? This will be at least
> > > consistent with below presentation[1] - eDP requirements depends on
> > > integrator. Then if olimex has standard way of dealing with panels
> > > present in olimex/teres platforms the driver would then create
> > > drm_panel/drm_connector/drm_bridge(?) according to these rules, I guess.
> > > Anyway it still looks fishy for me :), maybe because I am not
> > > familiarized with details of these platforms.
> >
> > That makes sense yes
>
> Actually, it makes no sense at all. Current implementation for anx6345
> driver works fine as is with any panel specified assuming panel delays
> are long enough for connected panel. It just doesn't use panel timings
> from the driver. Creating a platform driver for connector itself looks
> redundant since it can't be reused, it doesn't describe actual
> hardware and it's just defeats purpose of DT by introducing
> board-specific code.

I'm not sure where you got the idea that the purpose of DT is to not
have any board-specific code.

It's perfectly fine to have some, that's even why there's a compatible
assigned to each and every board.

What the DT is about is allowing us to have a generic behaviour that
we can detect: we can have a given behaviour for a given board, and a
separate one for another one, and this will be evaluated at runtime.

This is *exactly* what this is about: we can have a compatible that
sets a given, more specific, behaviour (olimex,teres-edp-connector)
while saying that this is compatible with the generic behaviour
(edp-connector). That way, any OS will know what quirk to apply if
needed, and if not that it can use the generic behaviour.

And we could create a generic driver, for the generic behaviour if
needed.

> There's another issue: if we introduce edp-connector we'll have to
> specify power up delays somewhere (in dts? or in platform driver?), so
> edp-connector doesn't really solve the issue of multiple panels with
> same motherboard.

And that's what that compatible is about :)

> I'd say DT overlays should be preferred solution here, not another
> connector binding.

Overlays are a way to apply a device tree dynamically. It's orthogonal
to the binding.

Maxime

--
Maxime Ripard, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply

* Re: [PATCH 3/3] arm64: dts: renesas: hihope-common: Add HDMI audio support
From: Simon Horman @ 2019-07-09  8:46 UTC (permalink / raw)
  To: Fabrizio Castro
  Cc: Geert Uytterhoeven, Rob Herring, Mark Rutland, Magnus Damm,
	linux-renesas-soc, devicetree, Chris Paterson, Biju Das,
	xu_shunji
In-Reply-To: <1562333979-28516-4-git-send-email-fabrizio.castro@bp.renesas.com>

On Fri, Jul 05, 2019 at 02:39:39PM +0100, Fabrizio Castro wrote:
> This patch adds support for HDMI audio to the device tree
> common to the HiHope RZ/G2M and the HiHope RZ/G2N.
> 
> Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>

Reviewed-by: Simon Horman <horms+renesas@verge.net.au>

^ permalink raw reply

* Re: [PATCH 2/3] arm64: dts: renesas: r8a774a1: Add SSIU support for sound
From: Simon Horman @ 2019-07-09  8:46 UTC (permalink / raw)
  To: Fabrizio Castro
  Cc: Geert Uytterhoeven, Rob Herring, Mark Rutland, Magnus Damm,
	linux-renesas-soc, devicetree, Chris Paterson, Biju Das
In-Reply-To: <1562333979-28516-3-git-send-email-fabrizio.castro@bp.renesas.com>

On Fri, Jul 05, 2019 at 02:39:38PM +0100, Fabrizio Castro wrote:
> Add SSIU support to the SoC DT as the sound driver supports
> it now, and also since the sound driver can now handle
> BUSIF0-7 via SSIU remove the no longer needed "rxu" and "txu"
> properties.
> 
> Based on similar work from Kuninori Morimoto and Simon Horman:
> 8d14bfa074db ("arm64: dts: renesas: r8a7796: add SSIU support for
> sound")
> 10bd03fa896e ("arm64: dts: renesas: r8a7796: remove BUSIF0 settings from
> rcar_sound,ssi")
> 
> Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>

Reviewed-by: Simon Horman <horms+renesas@verge.net.au>

^ permalink raw reply

* Re: [PATCH 1/3] arm64: dts: renesas: r8a774a1: Use extended audio dmac register
From: Simon Horman @ 2019-07-09  8:46 UTC (permalink / raw)
  To: Fabrizio Castro
  Cc: Geert Uytterhoeven, Rob Herring, Mark Rutland, Magnus Damm,
	linux-renesas-soc, devicetree, Chris Paterson, Biju Das
In-Reply-To: <1562333979-28516-2-git-send-email-fabrizio.castro@bp.renesas.com>

On Fri, Jul 05, 2019 at 02:39:37PM +0100, Fabrizio Castro wrote:
> Basic audio dmac register only supports busif from 0 to 3,
> in order to use busif4 ~ busif7 extended audio dmac registers
> need to be used.
> 
> Based on similar work from Jiada Wang:
> 7a516e49d975 ("arm64: dts: renesas: use extended audio dmac register")
> 
> Signed-off-by: Fabrizio Castro <fabrizio.castro@bp.renesas.com>

Reviewed-by: Simon Horman <horms+renesas@verge.net.au>

^ permalink raw reply

* [RFC PATCH V2 4/4] platform: mtk-isp: Add Mediatek FD driver
From: Jerry-ch Chen @ 2019-07-09  8:41 UTC (permalink / raw)
  To: hans.verkuil, laurent.pinchart+renesas, tfiga, matthias.bgg,
	mchehab
  Cc: shik, devicetree, Sean.Cheng, Rynn.Wu, srv_heupstream,
	po-yang.huang, suleiman, Jerry-ch Chen, jungo.lin, sj.huang,
	yuzhao, linux-mediatek, zwisler, christie.yu, frederic.chen,
	linux-arm-kernel, linux-media
In-Reply-To: <1562661672-22439-1-git-send-email-Jerry-Ch.chen@mediatek.com>

From: Jerry-ch Chen <jerry-ch.chen@mediatek.com>

This patch adds the driver of Face Detection (FD) unit in
Mediatek camera system, providing face detection function.

The mtk-isp directory will contain drivers for multiple IP
blocks found in Mediatek ISP system. It will include ISP Pass 1
driver (CAM), sensor interface driver, DIP driver and face
detection driver.

Signed-off-by: Jerry-ch Chen <jerry-ch.chen@mediatek.com>
---
 drivers/media/platform/Makefile               |    2 +
 drivers/media/platform/mtk-isp/fd/Makefile    |    5 +
 drivers/media/platform/mtk-isp/fd/mtk_fd.h    |  157 +++
 drivers/media/platform/mtk-isp/fd/mtk_fd_40.c | 1259 +++++++++++++++++++++++++
 include/uapi/linux/v4l2-controls.h            |    4 +
 5 files changed, 1427 insertions(+)
 create mode 100644 drivers/media/platform/mtk-isp/fd/Makefile
 create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd.h
 create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd_40.c

diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index e6deb25..8b817cc 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -94,6 +94,8 @@ obj-$(CONFIG_VIDEO_MEDIATEK_MDP)	+= mtk-mdp/
 
 obj-$(CONFIG_VIDEO_MEDIATEK_JPEG)	+= mtk-jpeg/
 
+obj-$(CONFIG_VIDEO_MEDIATEK_FD)		+= mtk-isp/fd/
+
 obj-$(CONFIG_VIDEO_QCOM_CAMSS)		+= qcom/camss/
 
 obj-$(CONFIG_VIDEO_QCOM_VENUS)		+= qcom/venus/
diff --git a/drivers/media/platform/mtk-isp/fd/Makefile b/drivers/media/platform/mtk-isp/fd/Makefile
new file mode 100644
index 0000000..9b1c501
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/fd/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+mtk-fd-objs += mtk_fd_40.o
+
+obj-$(CONFIG_VIDEO_MEDIATEK_FD) += mtk-fd.o
\ No newline at end of file
diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd.h b/drivers/media/platform/mtk-isp/fd/mtk_fd.h
new file mode 100644
index 0000000..289999b
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/fd/mtk_fd.h
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+//
+// Copyright (c) 2018 MediaTek Inc.
+
+#ifndef __MTK_FD_HW_H__
+#define __MTK_FD_HW_H__
+
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-v4l2.h>
+
+#define MTK_FD_OUTPUT_MIN_WIDTH			26U
+#define MTK_FD_OUTPUT_MIN_HEIGHT		26U
+#define MTK_FD_OUTPUT_MAX_WIDTH			640U
+#define MTK_FD_OUTPUT_MAX_HEIGHT		480U
+
+/* Control the user defined image widths and heights
+ * to be scaled and performed face detection in FD HW.
+ * MTK FD support up to 14 user defined image sizes to perform face detection.
+ */
+#define V4L2_CID_MTK_FD_SCALE_IMG_WIDTH		(V4L2_CID_USER_MTK_FD_BASE + 1)
+#define V4L2_CID_MTK_FD_SCALE_IMG_HEIGHT	(V4L2_CID_USER_MTK_FD_BASE + 2)
+
+/* Control the numbers of user defined image sizes.
+ * The default value is 0 which means user is not going
+ * to define the specific image sizes.
+ */
+#define V4L2_CID_MTK_FD_SCALE_IMG_NUM		(V4L2_CID_USER_MTK_FD_BASE + 3)
+
+/* Control the Face Pose to be detected.
+ * Here describe the value as following:
+ * {0, detect the front face with rotation from 0 to 270 degrees},
+ * {1, detect the front face with rotation from 0 to 240 and 300 degrees},
+ * {2, detect the front face with rotation from 0 to 240 and 330 degrees},
+ * {3, detect the front face with rotation from 0 to 240 and left side face}.
+ */
+#define V4L2_CID_MTK_FD_DETECT_POSE		(V4L2_CID_USER_MTK_FD_BASE + 4)
+#define V4L2_CID_MTK_FD_DETECT_SPEEDUP		(V4L2_CID_USER_MTK_FD_BASE + 5)
+#define V4L2_CID_MTK_FD_EXTRA_MODEL		(V4L2_CID_USER_MTK_FD_BASE + 6)
+
+/* We reserve 16 controls for this driver. */
+#define V4L2_CID_MTK_FD_MAX			16
+
+#define ENABLE_FD				0x111
+#define FD_HW_ENABLE				0x4
+#define FD_INT_EN				0x15c
+#define FD_INT					0x168
+#define FD_RESULT				0x178
+#define FD_IRQ_MASK				0x001
+
+#define RS_MAX_BUF_SIZE				2288788
+#define FD_MAX_SPEEDUP				7
+#define FD_MAX_POSE_VAL				0xfffffffffffffff
+#define FD_DEF_POSE_VAL				0x3ff
+#define MAX_FD_SEL_NUM				1026
+
+/* The max. number of face sizes could be detected, for feature scaling */
+#define FACE_SIZE_NUM_MAX			14
+/* FACE_SIZE_NUM_MAX + 1, first scale for input image W/H */
+#define FD_SCALE_NUM				15
+
+enum fd_state {
+	FD_ENQ,
+	FD_CBD,
+};
+
+enum fd_img_format {
+	FMT_VYUY = 2,
+	FMT_UYVY,
+	FMT_YVYU,
+	FMT_YUYV,
+	FMT_UNKNOWN
+};
+
+struct fd_buffer {
+	__u32 scp_addr;	/* used by SCP */
+	__u32 dma_addr;	/* used by DMA HW */
+} __packed;
+
+enum fd_scp_cmd {
+	FD_CMD_INIT,
+	FD_CMD_ENQUEUE,
+};
+
+struct fd_user_output {
+	__u64 results[MAX_FD_SEL_NUM];
+	__u16 number;
+};
+
+struct user_param {
+	u8 fd_pose;
+	u8 fd_speedup;
+	u8 fd_extra_model;
+	u8 scale_img_num;
+	u8 src_img_fmt;
+	__u16 scale_img_width[FD_SCALE_NUM];
+	__u16 scale_img_height[FD_SCALE_NUM];
+} __packed;
+
+struct fd_hw_param {
+	struct fd_buffer src_img;
+	struct fd_buffer user_result;
+	struct user_param user_param;
+} __packed;
+
+struct cmd_init_info {
+	struct fd_buffer fd_manager;
+	__u32 rs_dma_addr;
+} __packed;
+
+struct ipi_message {
+	u8 cmd_id;
+	union {
+		struct cmd_init_info init_param;
+		struct fd_hw_param hw_param;
+	};
+} __packed;
+
+struct mtk_fd_hw {
+	struct clk *fd_clk;
+	struct rproc *rproc_handle;
+	struct platform_device *scp_pdev;
+	struct fd_buffer scp_mem;
+	wait_queue_head_t wq;
+	void __iomem *fd_base;
+	atomic_t fd_user_cnt;
+	enum fd_state state;
+	u32 fd_irq_result;
+	/* Ensure only one job in hw */
+	struct mutex fd_hw_lock;
+};
+
+struct mtk_fd_dev {
+	struct platform_device *pdev;
+	struct v4l2_device v4l2_dev;
+	struct v4l2_m2m_dev *m2m_dev;
+	struct media_device mdev;
+	struct video_device vfd;
+	struct mtk_fd_hw fd_hw;
+	/* Lock for V4L2 operations */
+	struct mutex vfd_lock;
+};
+
+struct mtk_fd_ctx {
+	struct mtk_fd_dev *fd_dev;
+	struct device *dev;
+	struct v4l2_fh fh;
+	struct v4l2_ctrl_handler hdl;
+	struct v4l2_pix_format_mplane src_fmt;
+	struct v4l2_meta_format dst_fmt;
+	struct user_param user_param;
+};
+
+#endif/*__MTK_FD_HW_H__*/
diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd_40.c b/drivers/media/platform/mtk-isp/fd/mtk_fd_40.c
new file mode 100644
index 0000000..246d3aa
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/fd/mtk_fd_40.c
@@ -0,0 +1,1259 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2018 MediaTek Inc.
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/mtk_scp.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/remoteproc.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+#include <linux/wait.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-core.h>
+
+#include "mtk_fd.h"
+
+static struct v4l2_meta_format fw_param_fmts[] = {
+	{
+		.dataformat = V4L2_META_FMT_MTISP_PARAMS,
+		.buffersize = 1024 * 30,
+	},
+};
+
+static const struct v4l2_pix_format_mplane in_img_fmts[] = {
+	{
+		.width = MTK_FD_OUTPUT_MAX_WIDTH,
+		.height = MTK_FD_OUTPUT_MAX_HEIGHT,
+		.pixelformat = V4L2_PIX_FMT_VYUY,
+		.colorspace = V4L2_COLORSPACE_BT2020,
+		.field = V4L2_FIELD_NONE,
+		.num_planes = 1,
+	},
+	{
+		.width = MTK_FD_OUTPUT_MAX_WIDTH,
+		.height = MTK_FD_OUTPUT_MAX_HEIGHT,
+		.pixelformat = V4L2_PIX_FMT_YUYV,
+		.colorspace = V4L2_COLORSPACE_BT2020,
+		.field = V4L2_FIELD_NONE,
+		.num_planes = 1,
+	},
+	{
+		.width = MTK_FD_OUTPUT_MAX_WIDTH,
+		.height = MTK_FD_OUTPUT_MAX_HEIGHT,
+		.pixelformat = V4L2_PIX_FMT_YVYU,
+		.colorspace = V4L2_COLORSPACE_BT2020,
+		.field = V4L2_FIELD_NONE,
+		.num_planes = 1,
+	},
+	{
+		.width = MTK_FD_OUTPUT_MAX_WIDTH,
+		.height = MTK_FD_OUTPUT_MAX_HEIGHT,
+		.pixelformat = V4L2_PIX_FMT_UYVY,
+		.colorspace = V4L2_COLORSPACE_BT2020,
+		.field = V4L2_FIELD_NONE,
+		.num_planes = 1,
+	},
+};
+
+#define NUM_FORMATS ARRAY_SIZE(in_img_fmts)
+
+static inline struct mtk_fd_dev *mtk_fd_hw_to_dev(struct mtk_fd_hw *fd_hw)
+{
+	return container_of(fd_hw, struct mtk_fd_dev, fd_hw);
+}
+
+static inline struct mtk_fd_ctx *fh_to_ctx(struct v4l2_fh *fh)
+{
+	return container_of(fh, struct mtk_fd_ctx, fh);
+}
+
+static int mtk_fd_load_scp(struct mtk_fd_hw *fd_hw)
+{
+	struct mtk_fd_dev *fd_dev = mtk_fd_hw_to_dev(fd_hw);
+	struct device *dev = &fd_dev->pdev->dev;
+	phandle rproc_phandle;
+	int ret;
+
+	/* init scp */
+	fd_hw->scp_pdev = scp_get_pdev(fd_dev->pdev);
+	if (!fd_hw->scp_pdev) {
+		dev_err(dev, "Failed to get scp device\n");
+		return -ENODEV;
+	}
+
+	if (of_property_read_u32(fd_dev->pdev->dev.of_node, "mediatek,scp",
+				 &rproc_phandle)) {
+		dev_err(dev, "Could not get scp device\n");
+		return -EINVAL;
+	}
+
+	fd_hw->rproc_handle = rproc_get_by_phandle(rproc_phandle);
+	if (!fd_hw->rproc_handle) {
+		dev_err(dev, "Could not get FD's rproc_handle\n");
+		return -EINVAL;
+	}
+
+	ret = rproc_boot(fd_hw->rproc_handle);
+	if (ret < 0) {
+		/**
+		 * Return 0 if downloading firmware successfully,
+		 * otherwise it is failed
+		 */
+		dev_err(dev, "rproc_boot failed\n");
+		return -ENODEV;
+	}
+
+	return ret;
+}
+
+static dma_addr_t mtk_fd_hw_alloc_rs_dma_addr(struct mtk_fd_hw *fd_hw)
+{
+	struct mtk_fd_dev *fd_dev = mtk_fd_hw_to_dev(fd_hw);
+	struct device *dev = &fd_dev->pdev->dev;
+	void *va;
+	dma_addr_t dma_handle;
+
+	va = dma_alloc_coherent(dev, RS_MAX_BUF_SIZE, &dma_handle, GFP_KERNEL);
+	if (!va) {
+		dev_err(dev, "dma_alloc null va\n");
+		return -ENOMEM;
+	}
+	memset(va, 0, RS_MAX_BUF_SIZE);
+
+	return dma_handle;
+}
+
+static int mtk_fd_send_ipi_init(struct mtk_fd_hw *fd_hw)
+{
+	struct ipi_message fd_init_msg;
+	dma_addr_t rs_dma_addr;
+
+	fd_init_msg.cmd_id = FD_CMD_INIT;
+
+	fd_init_msg.init_param.fd_manager.scp_addr = fd_hw->scp_mem.scp_addr;
+	fd_init_msg.init_param.fd_manager.dma_addr = fd_hw->scp_mem.dma_addr;
+
+	rs_dma_addr = mtk_fd_hw_alloc_rs_dma_addr(fd_hw);
+	if (!rs_dma_addr)
+		return -ENOMEM;
+
+	fd_init_msg.init_param.rs_dma_addr = rs_dma_addr;
+
+	return scp_ipi_send(fd_hw->scp_pdev, SCP_IPI_FD_CMD, &fd_init_msg,
+			    sizeof(fd_init_msg), 0);
+}
+
+static int mtk_fd_hw_enable(struct mtk_fd_hw *fd_hw)
+{
+	int ret;
+
+	ret = mtk_fd_load_scp(fd_hw);
+	if (ret)
+		return ret;
+
+	ret = mtk_fd_send_ipi_init(fd_hw);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int mtk_fd_hw_connect(struct mtk_fd_hw *fd_hw)
+{
+	struct mtk_fd_dev *fd_dev = mtk_fd_hw_to_dev(fd_hw);
+	s32 usercount;
+
+	mutex_lock(&fd_hw->fd_hw_lock);
+	usercount = atomic_inc_return(&fd_hw->fd_user_cnt);
+	if (usercount == 1) {
+		pm_runtime_get_sync(&fd_dev->pdev->dev);
+		if (mtk_fd_hw_enable(fd_hw)) {
+			pm_runtime_put_sync(&fd_dev->pdev->dev);
+			atomic_dec_return(&fd_hw->fd_user_cnt);
+			mutex_unlock(&fd_hw->fd_hw_lock);
+			return -EINVAL;
+		}
+	}
+	mutex_unlock(&fd_hw->fd_hw_lock);
+
+	return 0;
+}
+
+static int mtk_fd_wait_irq(struct mtk_fd_hw *fd_hw)
+{
+	int timeout;
+	struct mtk_fd_dev *fd_dev = mtk_fd_hw_to_dev(fd_hw);
+	struct device *dev = &fd_dev->pdev->dev;
+
+	timeout = wait_event_interruptible_timeout
+		(fd_hw->wq, (fd_hw->fd_irq_result & FD_IRQ_MASK),
+		 usecs_to_jiffies(1000000));
+	if (!timeout) {
+		dev_err(dev, "%s timeout, %d\n", __func__,
+			fd_hw->fd_irq_result);
+		return -EAGAIN;
+	} else if (!(fd_hw->fd_irq_result & FD_IRQ_MASK)) {
+		dev_err(dev, "%s No IRQ mask:0x%8x\n",
+			__func__, fd_hw->fd_irq_result);
+		return -EINVAL;
+	}
+	fd_hw->fd_irq_result = 0;
+
+	return 0;
+}
+
+static void mtk_fd_hw_disconnect(struct mtk_fd_hw *fd_hw)
+{
+	struct mtk_fd_dev *fd_dev = mtk_fd_hw_to_dev(fd_hw);
+	s32 usercount;
+
+	mutex_lock(&fd_hw->fd_hw_lock);
+	atomic_dec_return(&fd_hw->fd_user_cnt);
+	usercount = atomic_read(&fd_hw->fd_user_cnt);
+	if (usercount == 0) {
+		if (fd_hw->state == FD_ENQ)
+			mtk_fd_wait_irq(fd_hw);
+
+		pm_runtime_put_sync(&fd_dev->pdev->dev);
+		rproc_shutdown(fd_hw->rproc_handle);
+		rproc_put(fd_hw->rproc_handle);
+	}
+	mutex_unlock(&fd_hw->fd_hw_lock);
+}
+
+static void mtk_fd_hw_job_finish(struct mtk_fd_hw *fd_hw,
+				 struct fd_hw_param *fd_param,
+				 enum vb2_buffer_state vb_state)
+{
+	struct mtk_fd_dev *fd_dev = mtk_fd_hw_to_dev(fd_hw);
+	struct mtk_fd_ctx *ctx;
+	struct device *dev = &fd_dev->pdev->dev;
+	struct vb2_buffer *src_vb, *dst_vb;
+	struct vb2_v4l2_buffer *src_vbuf = NULL, *dst_vbuf = NULL;
+
+	ctx = v4l2_m2m_get_curr_priv(fd_dev->m2m_dev);
+	if (!ctx) {
+		dev_err(dev, "Instance released before end of transaction\n");
+		return;
+	}
+
+	src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+	if (WARN_ON(!src_vb))
+		return;
+	src_vbuf = to_vb2_v4l2_buffer(src_vb);
+
+	dst_vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+	if (WARN_ON(!dst_vb))
+		return;
+	dst_vbuf = to_vb2_v4l2_buffer(dst_vb);
+
+	dst_vbuf->vb2_buf.timestamp = src_vbuf->vb2_buf.timestamp;
+	dst_vbuf->timecode = src_vbuf->timecode;
+	dst_vbuf->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+	dst_vbuf->flags |= src_vbuf->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+
+	v4l2_m2m_buf_done(src_vbuf, vb_state);
+	v4l2_m2m_buf_done(dst_vbuf, vb_state);
+	v4l2_m2m_job_finish(fd_dev->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+static int mtk_fd_hw_job_exec(struct mtk_fd_hw *fd_hw,
+			      struct fd_hw_param *fd_param,
+			      void *output_vaddr)
+{
+	struct fd_user_output *fd_output;
+	struct ipi_message fd_ipi_msg;
+	int ret;
+	u32 num;
+
+	if (fd_param->user_param.src_img_fmt == FMT_UNKNOWN)
+		goto param_err;
+
+	mutex_lock(&fd_hw->fd_hw_lock);
+	fd_hw->state = FD_ENQ;
+	fd_ipi_msg.cmd_id = FD_CMD_ENQUEUE;
+	memcpy(&fd_ipi_msg.hw_param, fd_param, sizeof(fd_ipi_msg.hw_param));
+	ret = scp_ipi_send(fd_hw->scp_pdev, SCP_IPI_FD_CMD, &fd_ipi_msg,
+			   sizeof(fd_ipi_msg), 0);
+	if (ret)
+		goto buf_err;
+
+	ret = mtk_fd_wait_irq(fd_hw);
+	if (ret)
+		goto buf_err;
+
+	num = readl(fd_hw->fd_base + FD_RESULT);
+	/* Disable FD ISR */
+	writel(0x0, fd_hw->fd_base + FD_INT_EN);
+
+	fd_output = (struct fd_user_output *)output_vaddr;
+	fd_output->number = num;
+	fd_hw->state = FD_CBD;
+	mutex_unlock(&fd_hw->fd_hw_lock);
+
+	mtk_fd_hw_job_finish(fd_hw, fd_param, VB2_BUF_STATE_DONE);
+	return 0;
+
+buf_err:
+	mutex_unlock(&fd_hw->fd_hw_lock);
+param_err:
+	mtk_fd_hw_job_finish(fd_hw, fd_param, VB2_BUF_STATE_ERROR);
+	return ret;
+}
+
+static int mtk_fd_vb2_buf_out_validate(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
+
+	v4l2_buf->field = V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+static int mtk_fd_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct vb2_queue *vq = vb->vb2_queue;
+	struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
+	struct device *dev = ctx->dev;
+	struct v4l2_pix_format_mplane *pixfmt;
+
+	switch (vq->type) {
+	case V4L2_BUF_TYPE_META_CAPTURE:
+		if (vb2_plane_size(vb, 0) < ctx->dst_fmt.buffersize) {
+			dev_err(dev, "meta size %d is too small\n");
+			return -EINVAL;
+		}
+		break;
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+		pixfmt = &ctx->src_fmt;
+
+		if (vbuf->field == V4L2_FIELD_ANY)
+			vbuf->field = V4L2_FIELD_NONE;
+
+		if (vb->num_planes != 1 || vbuf->field != V4L2_FIELD_NONE) {
+			dev_err(dev, "plane or field %d not supported\n",
+				vb->num_planes, vbuf->field);
+			return -EINVAL;
+		}
+		if (vb2_plane_size(vb, 0) < pixfmt->plane_fmt[0].sizeimage) {
+			dev_err(dev, "plane %d is too small\n");
+			return -EINVAL;
+		}
+		break;
+	default:
+		dev_err(dev, "invalid queue type: %d\n", vq->type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void mtk_fd_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static int mtk_fd_vb2_queue_setup(struct vb2_queue *vq,
+				  unsigned int *num_buffers,
+				  unsigned int *num_planes,
+				  unsigned int sizes[],
+				  struct device *alloc_devs[])
+{
+	struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
+	struct device *dev = ctx->dev;
+	unsigned int size;
+
+	switch (vq->type) {
+	case V4L2_BUF_TYPE_META_CAPTURE:
+		size = ctx->dst_fmt.buffersize;
+		break;
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+		size = ctx->src_fmt.plane_fmt[0].sizeimage;
+		break;
+	default:
+		dev_err(dev, "invalid queue type: %d\n", vq->type);
+		return -EINVAL;
+	}
+
+	if (!*num_planes) {
+		*num_planes = 1;
+		sizes[0] = size;
+	}
+
+	return 0;
+}
+
+static int mtk_fd_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
+
+	return mtk_fd_hw_connect(&ctx->fd_dev->fd_hw);
+}
+
+static void mtk_fd_vb2_stop_streaming(struct vb2_queue *vq)
+{
+	struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
+	struct vb2_buffer *vb;
+
+	if (V4L2_TYPE_IS_OUTPUT(vq->type))
+		vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+	else
+		vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+
+	while (vb) {
+		v4l2_m2m_buf_done(to_vb2_v4l2_buffer(vb), VB2_BUF_STATE_ERROR);
+		if (V4L2_TYPE_IS_OUTPUT(vq->type))
+			vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+		else
+			vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+	}
+
+	mtk_fd_hw_disconnect(&ctx->fd_dev->fd_hw);
+}
+
+static void mtk_fd_vb2_request_complete(struct vb2_buffer *vb)
+{
+	struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+	v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl);
+}
+
+static void mtk_fd_fill_pixfmt_mp(struct v4l2_pix_format_mplane *dfmt,
+				  const struct v4l2_pix_format_mplane *sfmt)
+{
+	dfmt->width = sfmt->width;
+	dfmt->height = sfmt->height;
+	dfmt->pixelformat = sfmt->pixelformat;
+	dfmt->field = sfmt->field;
+	dfmt->colorspace = sfmt->colorspace;
+	dfmt->num_planes = sfmt->num_planes;
+
+	/* Use default */
+	dfmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+	dfmt->quantization = V4L2_QUANTIZATION_DEFAULT;
+	dfmt->xfer_func =
+		V4L2_MAP_XFER_FUNC_DEFAULT(dfmt->colorspace);
+	dfmt->plane_fmt[0].bytesperline = dfmt->width * 2;
+	dfmt->plane_fmt[0].sizeimage =
+		dfmt->height * dfmt->plane_fmt[0].bytesperline;
+	memset(dfmt->reserved, 0, sizeof(dfmt->reserved));
+}
+
+static const struct v4l2_pix_format_mplane *mtk_fd_find_fmt(u32 format)
+{
+	unsigned int i;
+	const struct v4l2_pix_format_mplane *dev_fmt;
+
+	for (i = 0; i < NUM_FORMATS; i++) {
+		dev_fmt = &in_img_fmts[i];
+		if (dev_fmt->pixelformat == format)
+			return dev_fmt;
+	}
+
+	return NULL;
+}
+
+static int mtk_fd_m2m_querycap(struct file *file, void *fh,
+			       struct v4l2_capability *cap)
+{
+	struct mtk_fd_dev *fd_dev = video_drvdata(file);
+	struct device *dev = &fd_dev->pdev->dev;
+
+	strscpy(cap->driver, dev->driver->name, sizeof(cap->driver));
+	strscpy(cap->card, fd_dev->vfd.name, sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+		 dev_name(&fd_dev->pdev->dev));
+
+	return 0;
+}
+
+static int mtk_fd_m2m_enum_fmt_out_mp(struct file *file, void *fh,
+				      struct v4l2_fmtdesc *f)
+{
+	int i;
+
+	for (i = 0; i < NUM_FORMATS; ++i) {
+		if (i == f->index) {
+			f->pixelformat = in_img_fmts[i].pixelformat;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int mtk_fd_m2m_try_fmt_out_mp(struct file *file,
+				     void *fh,
+				     struct v4l2_format *f)
+{
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	const struct v4l2_pix_format_mplane *fmt;
+
+	fmt = mtk_fd_find_fmt(pix_mp->pixelformat);
+	if (!fmt) {
+		/* Get default img fmt */
+		fmt = &in_img_fmts[0];
+		f->fmt.pix.pixelformat = fmt->pixelformat;
+	}
+
+	/* Use default */
+	pix_mp->field = fmt->field;
+	pix_mp->colorspace = fmt->colorspace;
+	pix_mp->num_planes = fmt->num_planes;
+	pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+	pix_mp->quantization = V4L2_QUANTIZATION_DEFAULT;
+	pix_mp->xfer_func =
+		V4L2_MAP_XFER_FUNC_DEFAULT(pix_mp->colorspace);
+
+	/* Keep user setting as possible */
+	pix_mp->width = clamp(pix_mp->width,
+			      MTK_FD_OUTPUT_MIN_WIDTH,
+			      MTK_FD_OUTPUT_MAX_WIDTH);
+	pix_mp->height = clamp(pix_mp->height,
+			       MTK_FD_OUTPUT_MIN_HEIGHT,
+			       MTK_FD_OUTPUT_MAX_HEIGHT);
+
+	pix_mp->plane_fmt[0].bytesperline = pix_mp->width * 2;
+	pix_mp->plane_fmt[0].sizeimage =
+		pix_mp->plane_fmt[0].bytesperline * pix_mp->height;
+	memset(pix_mp->plane_fmt[0].reserved, 0,
+	       sizeof(pix_mp->plane_fmt[0].reserved));
+
+	return 0;
+}
+
+static int mtk_fd_m2m_g_fmt_out_mp(struct file *file, void *fh,
+				   struct v4l2_format *f)
+{
+	struct mtk_fd_ctx *ctx = fh_to_ctx(fh);
+
+	f->fmt.pix_mp = ctx->src_fmt;
+
+	return 0;
+}
+
+static int mtk_fd_m2m_s_fmt_out_mp(struct file *file, void *fh,
+				   struct v4l2_format *f)
+{
+	struct mtk_fd_ctx *ctx = fh_to_ctx(fh);
+	struct vb2_queue *vq;
+
+	/* Change not allowed if queue is streaming. */
+	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+	if (vb2_is_streaming(vq) || vb2_is_busy(vq)) {
+		dev_dbg(ctx->dev, "vb2_is_streaming or vb2_is_busy");
+		return -EBUSY;
+	}
+
+	mtk_fd_m2m_try_fmt_out_mp(file, fh, f);
+	ctx->src_fmt = f->fmt.pix_mp;
+
+	return 0;
+}
+
+static int mtk_fd_m2m_enum_fmt_meta_cap(struct file *file, void *fh,
+					struct v4l2_fmtdesc *f)
+{
+	if (f->index)
+		return -EINVAL;
+
+	strscpy(f->description, "Face detection result",
+		sizeof(f->description));
+	f->pixelformat = fw_param_fmts[0].dataformat;
+	f->flags = 0;
+
+	return 0;
+}
+
+static int mtk_fd_m2m_g_fmt_meta_cap(struct file *file, void *fh,
+				     struct v4l2_format *f)
+{
+	f->fmt.meta.dataformat = fw_param_fmts[0].dataformat;
+	f->fmt.meta.buffersize = fw_param_fmts[0].buffersize;
+
+	return 0;
+}
+
+static const struct vb2_ops mtk_fd_vb2_ops = {
+	.queue_setup = mtk_fd_vb2_queue_setup,
+	.buf_out_validate = mtk_fd_vb2_buf_out_validate,
+	.buf_prepare  = mtk_fd_vb2_buf_prepare,
+	.buf_queue = mtk_fd_vb2_buf_queue,
+	.start_streaming = mtk_fd_vb2_start_streaming,
+	.stop_streaming = mtk_fd_vb2_stop_streaming,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.buf_request_complete = mtk_fd_vb2_request_complete,
+};
+
+static const struct v4l2_ioctl_ops mtk_fd_v4l2_video_out_ioctl_ops = {
+	.vidioc_querycap = mtk_fd_m2m_querycap,
+	.vidioc_enum_fmt_vid_out_mplane = mtk_fd_m2m_enum_fmt_out_mp,
+	.vidioc_g_fmt_vid_out_mplane = mtk_fd_m2m_g_fmt_out_mp,
+	.vidioc_s_fmt_vid_out_mplane = mtk_fd_m2m_s_fmt_out_mp,
+	.vidioc_try_fmt_vid_out_mplane = mtk_fd_m2m_try_fmt_out_mp,
+	.vidioc_enum_fmt_meta_cap = mtk_fd_m2m_enum_fmt_meta_cap,
+	.vidioc_g_fmt_meta_cap = mtk_fd_m2m_g_fmt_meta_cap,
+	.vidioc_s_fmt_meta_cap = mtk_fd_m2m_g_fmt_meta_cap,
+	.vidioc_try_fmt_meta_cap = mtk_fd_m2m_g_fmt_meta_cap,
+	.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+	.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+	.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+	.vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+	.vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+	.vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+	.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+	.vidioc_streamon = v4l2_m2m_ioctl_streamon,
+	.vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int
+mtk_fd_queue_init(void *priv, struct vb2_queue *src_vq,
+		  struct vb2_queue *dst_vq)
+{
+	struct mtk_fd_ctx *ctx = priv;
+	int ret;
+
+	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+	src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	src_vq->supports_requests = true;
+	src_vq->drv_priv = ctx;
+	src_vq->ops = &mtk_fd_vb2_ops;
+	src_vq->mem_ops = &vb2_dma_contig_memops;
+	src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	src_vq->lock = &ctx->fd_dev->vfd_lock;
+	src_vq->dev = ctx->fd_dev->v4l2_dev.dev;
+
+	ret = vb2_queue_init(src_vq);
+	if (ret)
+		return ret;
+
+	dst_vq->type = V4L2_BUF_TYPE_META_CAPTURE;
+	dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	dst_vq->drv_priv = ctx;
+	dst_vq->ops = &mtk_fd_vb2_ops;
+	dst_vq->mem_ops = &vb2_dma_contig_memops;
+	dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	dst_vq->lock = &ctx->fd_dev->vfd_lock;
+	dst_vq->dev = ctx->fd_dev->v4l2_dev.dev;
+
+	return vb2_queue_init(dst_vq);
+}
+
+static int mtk_fd_dev_g_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct mtk_fd_ctx *ctx = ctrl->priv;
+	int i;
+
+	switch (ctrl->id) {
+	case V4L2_CID_MTK_FD_SCALE_IMG_WIDTH:
+		for (i = 0; i < ctrl->elems; i++)
+			ctrl->p_new.p_u16[i] =
+				ctx->user_param.scale_img_width[i];
+		break;
+	case V4L2_CID_MTK_FD_SCALE_IMG_HEIGHT:
+		for (i = 0; i < ctrl->elems; i++)
+			ctrl->p_new.p_u16[i] =
+				ctx->user_param.scale_img_height[i];
+		break;
+	case V4L2_CID_MTK_FD_DETECT_POSE:
+		ctrl->val = ctx->user_param.fd_pose;
+		break;
+	case V4L2_CID_MTK_FD_DETECT_SPEEDUP:
+		ctrl->val = ctx->user_param.fd_speedup;
+		break;
+	case V4L2_CID_MTK_FD_SCALE_IMG_NUM:
+		ctrl->val = ctx->user_param.scale_img_num;
+		break;
+	case V4L2_CID_MTK_FD_EXTRA_MODEL:
+		ctrl->val = ctx->user_param.fd_extra_model;
+		break;
+	default:
+		dev_dbg(ctx->dev, "%s: unexpected control: 0x%x:%d",
+			__func__, ctrl->id, ctrl->val);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int mtk_fd_dev_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct mtk_fd_ctx *ctx = ctrl->priv;
+	int i;
+
+	switch (ctrl->id) {
+	case V4L2_CID_MTK_FD_SCALE_IMG_WIDTH:
+		for (i = 0; i < ctrl->elems; i++)
+			ctx->user_param.scale_img_width[i] =
+				ctrl->p_new.p_u16[i];
+		break;
+	case V4L2_CID_MTK_FD_SCALE_IMG_HEIGHT:
+		for (i = 0; i < ctrl->elems; i++)
+			ctx->user_param.scale_img_height[i] =
+				ctrl->p_new.p_u16[i];
+		break;
+	case V4L2_CID_MTK_FD_DETECT_POSE:
+		ctx->user_param.fd_pose = ctrl->val;
+		break;
+	case V4L2_CID_MTK_FD_DETECT_SPEEDUP:
+		ctx->user_param.fd_speedup = ctrl->val;
+		break;
+	case V4L2_CID_MTK_FD_SCALE_IMG_NUM:
+		ctx->user_param.scale_img_num = ctrl->val;
+		break;
+	case V4L2_CID_MTK_FD_EXTRA_MODEL:
+		ctx->user_param.fd_extra_model = ctrl->val;
+		break;
+	default:
+		dev_dbg(ctx->dev, "%s: unexpected control: 0x%x:%d",
+			__func__, ctrl->id, ctrl->val);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops mtk_fd_dev_ctrl_ops = {
+	.g_volatile_ctrl = mtk_fd_dev_g_ctrl,
+	.s_ctrl = mtk_fd_dev_s_ctrl,
+};
+
+struct v4l2_ctrl_config mtk_fd_controls[] = {
+	{
+	.ops = &mtk_fd_dev_ctrl_ops,
+	.id = V4L2_CID_MTK_FD_SCALE_IMG_WIDTH,
+	.name = "FD scale image widths",
+	.type = V4L2_CTRL_TYPE_U16,
+	.min = MTK_FD_OUTPUT_MIN_WIDTH,
+	.max = MTK_FD_OUTPUT_MAX_WIDTH,
+	.step = 1,
+	.def = MTK_FD_OUTPUT_MAX_WIDTH,
+	.dims = { FD_SCALE_NUM },
+	},
+	{
+	.ops = &mtk_fd_dev_ctrl_ops,
+	.id = V4L2_CID_MTK_FD_SCALE_IMG_HEIGHT,
+	.name = "FD scale image heights",
+	.type = V4L2_CTRL_TYPE_U16,
+	.min = MTK_FD_OUTPUT_MIN_HEIGHT,
+	.max = MTK_FD_OUTPUT_MAX_HEIGHT,
+	.step = 1,
+	.def = MTK_FD_OUTPUT_MAX_HEIGHT,
+	.dims = { FD_SCALE_NUM },
+	},
+	{
+	.ops = &mtk_fd_dev_ctrl_ops,
+	.id = V4L2_CID_MTK_FD_SCALE_IMG_NUM,
+	.name = "FD scale size counts",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0,
+	.max = FACE_SIZE_NUM_MAX,
+	.step = 1,
+	.def = 0,
+	},
+	{
+	.ops = &mtk_fd_dev_ctrl_ops,
+	.id = V4L2_CID_MTK_FD_DETECT_POSE,
+	.name = "FD detect face pose",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0,
+	.max = 3,
+	.step = 1,
+	.def = 0,
+	},
+	{
+	.ops = &mtk_fd_dev_ctrl_ops,
+	.id = V4L2_CID_MTK_FD_DETECT_SPEEDUP,
+	.name = "FD detection speedup",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0,
+	.max = FD_MAX_SPEEDUP,
+	.step = 1,
+	.def = 0,
+	},
+	{
+	.ops = &mtk_fd_dev_ctrl_ops,
+	.id = V4L2_CID_MTK_FD_EXTRA_MODEL,
+	.name = "FD use extra model",
+	.type = V4L2_CTRL_TYPE_BOOLEAN,
+	.min = 0,
+	.max = 1,
+	.step = 1,
+	.def = 0,
+	},
+};
+
+static int mtk_fd_ctrls_setup(struct mtk_fd_ctx *ctx)
+{
+	struct v4l2_ctrl_handler *hdl = &ctx->hdl;
+	struct v4l2_ctrl *ctl;
+	int i;
+
+	v4l2_ctrl_handler_init(hdl, V4L2_CID_MTK_FD_MAX);
+	if (hdl->error)
+		return hdl->error;
+
+	for (i = 0; i < ARRAY_SIZE(mtk_fd_controls); i++) {
+		ctl = v4l2_ctrl_new_custom(hdl, &mtk_fd_controls[i], ctx);
+		if (hdl->error) {
+			v4l2_ctrl_handler_free(hdl);
+			dev_err(ctx->dev, "Failed to register controls:%d", i);
+			return hdl->error;
+		}
+	}
+
+	ctx->fh.ctrl_handler = &ctx->hdl;
+	v4l2_ctrl_handler_setup(hdl);
+
+	return 0;
+}
+
+static unsigned int get_fd_img_fmt(unsigned int fourcc)
+{
+	switch (fourcc) {
+	case V4L2_PIX_FMT_VYUY:
+		return FMT_VYUY;
+	case V4L2_PIX_FMT_YUYV:
+		return FMT_YUYV;
+	case V4L2_PIX_FMT_YVYU:
+		return FMT_YVYU;
+	case V4L2_PIX_FMT_UYVY:
+		return FMT_UYVY;
+	default:
+		return FMT_UNKNOWN;
+	}
+}
+
+static void init_ctx_fmt(struct mtk_fd_ctx *ctx)
+{
+	const struct v4l2_pix_format_mplane *fmt;
+
+	/* Initialize M2M source fmt */
+	fmt = &in_img_fmts[0];
+	mtk_fd_fill_pixfmt_mp(&ctx->src_fmt, fmt);
+
+	/* Initialize M2M destination fmt */
+	ctx->dst_fmt.buffersize = fw_param_fmts[0].buffersize;
+	ctx->dst_fmt.dataformat = fw_param_fmts[0].dataformat;
+}
+
+/*
+ * V4L2 file operations.
+ */
+static int mtk_vfd_open(struct file *filp)
+{
+	struct mtk_fd_dev *fd_dev = video_drvdata(filp);
+	struct video_device *vdev = video_devdata(filp);
+	struct mtk_fd_ctx *ctx;
+	int ret;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	ctx->fd_dev = fd_dev;
+	ctx->dev = &fd_dev->pdev->dev;
+
+	v4l2_fh_init(&ctx->fh, vdev);
+	filp->private_data = &ctx->fh;
+
+	init_ctx_fmt(ctx);
+
+	ret = mtk_fd_ctrls_setup(ctx);
+	if (ret) {
+		dev_err(ctx->dev, "Failed to set up controls:%d\n", ret);
+		goto err_fh_ctrl;
+	}
+
+	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(fd_dev->m2m_dev, ctx,
+					    &mtk_fd_queue_init);
+	if (IS_ERR(ctx->fh.m2m_ctx)) {
+		ret = PTR_ERR(ctx->fh.m2m_ctx);
+		goto err_init_ctx;
+	}
+
+	v4l2_fh_add(&ctx->fh);
+
+	return 0;
+
+err_init_ctx:
+	v4l2_ctrl_handler_free(&ctx->hdl);
+err_fh_ctrl:
+	v4l2_fh_exit(&ctx->fh);
+	kfree(ctx);
+
+	return ret;
+}
+
+static int mtk_vfd_release(struct file *filp)
+{
+	struct mtk_fd_ctx *ctx = container_of(filp->private_data,
+					      struct mtk_fd_ctx, fh);
+
+	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+
+	v4l2_ctrl_handler_free(&ctx->hdl);
+	v4l2_fh_del(&ctx->fh);
+	v4l2_fh_exit(&ctx->fh);
+
+	kfree(ctx);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations fd_video_fops = {
+	.owner = THIS_MODULE,
+	.open = mtk_vfd_open,
+	.release = mtk_vfd_release,
+	.poll = v4l2_m2m_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = v4l2_m2m_fop_mmap,
+};
+
+static void mtk_fd_device_run(void *priv)
+{
+	struct mtk_fd_ctx *ctx = priv;
+	struct vb2_v4l2_buffer *src_buf, *dst_buf;
+	struct fd_hw_param fd_param;
+	void *fd_result_vaddr;
+
+	src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+	dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+
+	memset(&fd_param, 0, sizeof(fd_param));
+
+	fd_param.src_img.dma_addr =
+		vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0);
+	fd_param.user_result.dma_addr =
+		vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
+	fd_result_vaddr = vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
+
+	ctx->user_param.src_img_fmt = get_fd_img_fmt(ctx->src_fmt.pixelformat);
+	memcpy(&fd_param.user_param, &ctx->user_param, sizeof(ctx->user_param));
+
+	/* Complete request controls if any */
+	v4l2_ctrl_request_complete(src_buf->vb2_buf.req_obj.req, &ctx->hdl);
+
+	mtk_fd_hw_job_exec(&ctx->fd_dev->fd_hw, &fd_param, fd_result_vaddr);
+}
+
+static struct v4l2_m2m_ops fd_m2m_ops = {
+	.device_run = mtk_fd_device_run,
+};
+
+static int mtk_fd_request_validate(struct media_request *req)
+{
+	unsigned int count;
+
+	count = vb2_request_buffer_cnt(req);
+	if (!count)
+		return -ENOENT;
+	else if (count > 1)
+		return -EINVAL;
+
+	return vb2_request_validate(req);
+}
+
+static const struct media_device_ops fd_m2m_media_ops = {
+	.req_validate	= mtk_fd_request_validate,
+	.req_queue	= v4l2_m2m_request_queue,
+};
+
+static int mtk_fd_video_device_register(struct mtk_fd_dev *fd_dev)
+{
+	struct video_device *vfd = &fd_dev->vfd;
+	struct v4l2_m2m_dev *m2m_dev = fd_dev->m2m_dev;
+	struct device *dev = &fd_dev->pdev->dev;
+	int function, ret;
+
+	vfd->fops = &fd_video_fops;
+	vfd->release = video_device_release;
+	vfd->lock = &fd_dev->vfd_lock;
+	vfd->v4l2_dev = &fd_dev->v4l2_dev;
+	vfd->vfl_dir = VFL_DIR_M2M;
+	vfd->device_caps = V4L2_CAP_STREAMING  | V4L2_CAP_VIDEO_OUTPUT_MPLANE |
+		V4L2_CAP_META_CAPTURE;
+	vfd->ioctl_ops = &mtk_fd_v4l2_video_out_ioctl_ops;
+
+	strscpy(vfd->name, "MTK-FD-V4L2", sizeof(vfd->name));
+	video_set_drvdata(vfd, fd_dev);
+
+	ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0);
+	if (ret) {
+		dev_err(dev, "Failed to register video device\n");
+		goto err_free_dev;
+	}
+
+	function = MEDIA_ENT_F_PROC_VIDEO_DECODER;
+	ret = v4l2_m2m_register_media_controller(m2m_dev, vfd, function);
+	if (ret) {
+		dev_err(dev, "Failed to init mem2mem media controller\n");
+		goto err_unreg_video;
+	}
+	return 0;
+
+err_unreg_video:
+	video_unregister_device(vfd);
+err_free_dev:
+	video_device_release(vfd);
+	return ret;
+}
+
+static int mtk_fd_dev_v4l2_init(struct mtk_fd_dev *fd_dev)
+{
+	struct media_device *mdev = &fd_dev->mdev;
+	struct device *dev = &fd_dev->pdev->dev;
+	int ret;
+
+	ret = v4l2_device_register(dev, &fd_dev->v4l2_dev);
+	if (ret) {
+		dev_err(dev, "Failed to register v4l2 device\n");
+		return ret;
+	}
+
+	fd_dev->m2m_dev = v4l2_m2m_init(&fd_m2m_ops);
+	if (IS_ERR(fd_dev->m2m_dev)) {
+		dev_err(dev, "Failed to init mem2mem device\n");
+		ret = PTR_ERR(fd_dev->m2m_dev);
+		goto fail_m2m_dev;
+	}
+
+	mdev->dev = dev;
+	strscpy(mdev->model, "MTK-FD-V4L2", sizeof(mdev->model));
+	snprintf(mdev->bus_info, sizeof(mdev->bus_info),
+		 "platform:%s", dev_name(dev));
+	media_device_init(mdev);
+	mdev->ops = &fd_m2m_media_ops;
+	fd_dev->v4l2_dev.mdev = mdev;
+
+	ret = mtk_fd_video_device_register(fd_dev);
+	if (ret) {
+		dev_err(dev, "Failed to register video device\n");
+		goto err_vdev;
+	}
+
+	ret = media_device_register(mdev);
+	if (ret) {
+		dev_err(dev, "Failed to register mem2mem media device\n");
+		goto fail_mdev;
+	}
+
+	return 0;
+
+fail_mdev:
+	v4l2_m2m_unregister_media_controller(fd_dev->m2m_dev);
+	video_unregister_device(&fd_dev->vfd);
+	video_device_release(&fd_dev->vfd);
+err_vdev:
+	media_device_cleanup(mdev);
+	v4l2_m2m_release(fd_dev->m2m_dev);
+fail_m2m_dev:
+	v4l2_device_unregister(&fd_dev->v4l2_dev);
+	return ret;
+}
+
+static void mtk_fd_dev_v4l2_release(struct mtk_fd_dev *fd_dev)
+{
+	v4l2_m2m_unregister_media_controller(fd_dev->m2m_dev);
+	video_unregister_device(&fd_dev->vfd);
+	video_device_release(&fd_dev->vfd);
+	media_device_cleanup(&fd_dev->mdev);
+	v4l2_m2m_release(fd_dev->m2m_dev);
+	v4l2_device_unregister(&fd_dev->v4l2_dev);
+}
+
+static irqreturn_t mtk_fd_irq(int irq, void *data)
+{
+	struct mtk_fd_hw *fd_hw = (struct mtk_fd_hw *)data;
+
+	fd_hw->fd_irq_result = readl(fd_hw->fd_base + FD_INT);
+	wake_up_interruptible(&fd_hw->wq);
+	return IRQ_HANDLED;
+}
+
+static int mtk_fd_hw_get_scp_mem(struct mtk_fd_hw *fd_hw,
+				 struct fd_buffer *scp_mem)
+{
+	struct mtk_fd_dev *fd_dev = mtk_fd_hw_to_dev(fd_hw);
+	struct device *dev = &fd_dev->pdev->dev;
+	dma_addr_t addr;
+	u32 size;
+
+	scp_mem->scp_addr = scp_get_reserve_mem_phys(SCP_FD_MEM_ID);
+	size = scp_get_reserve_mem_size(SCP_FD_MEM_ID);
+	if (!scp_mem->scp_addr || !size)
+		return -EPROBE_DEFER;
+
+	/* get dma addr address */
+	addr = dma_map_page_attrs(dev, phys_to_page(scp_mem->scp_addr), 0,
+				  size, DMA_BIDIRECTIONAL,
+				  DMA_ATTR_SKIP_CPU_SYNC);
+	if (dma_mapping_error(dev, addr)) {
+		scp_mem->scp_addr = 0;
+		dev_err(dev, "Failed to map scp addr\n");
+		return -ENOMEM;
+	}
+	scp_mem->dma_addr = addr;
+
+	return 0;
+}
+
+static int mtk_fd_probe(struct platform_device *pdev)
+{
+	struct mtk_fd_dev *fd_dev;
+	struct device *dev = &pdev->dev;
+	struct mtk_fd_hw *fd_hw;
+	struct resource *res;
+	int irq;
+	int ret;
+
+	fd_dev = devm_kzalloc(&pdev->dev, sizeof(*fd_dev), GFP_KERNEL);
+	if (!fd_dev)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, fd_dev);
+	fd_hw = &fd_dev->fd_hw;
+	fd_dev->pdev = pdev;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "no IRQ:%d resource info\n", irq);
+		return irq;
+	}
+	ret = devm_request_irq(dev, irq, mtk_fd_irq, IRQF_SHARED,
+			       dev_driver_string(dev),
+			       fd_hw);
+	if (ret) {
+		dev_err(dev, "req_irq fail:%d\n", irq);
+		return ret;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	fd_hw->fd_base = devm_ioremap_resource(dev, res);
+	if (!fd_hw->fd_base) {
+		dev_err(dev, "unable to get fd reg base\n");
+		return PTR_ERR(fd_hw->fd_base);
+	}
+
+	fd_hw->fd_clk = devm_clk_get(dev, "fd");
+	if (IS_ERR(fd_hw->fd_clk)) {
+		dev_err(dev, "cannot get fd_clk_img_fd clock\n");
+		return PTR_ERR(fd_hw->fd_clk);
+	}
+
+	ret = mtk_fd_hw_get_scp_mem(fd_hw, &fd_hw->scp_mem);
+	if (ret) {
+		dev_err(dev, "scp memory init failed: %d\n", ret);
+		return ret;
+	}
+
+	atomic_set(&fd_hw->fd_user_cnt, 0);
+	init_waitqueue_head(&fd_hw->wq);
+	mutex_init(&fd_dev->vfd_lock);
+	mutex_init(&fd_hw->fd_hw_lock);
+	pm_runtime_enable(dev);
+
+	ret = mtk_fd_dev_v4l2_init(fd_dev);
+	if (ret) {
+		mutex_destroy(&fd_dev->fd_hw.fd_hw_lock);
+		mutex_destroy(&fd_dev->vfd_lock);
+		pm_runtime_disable(&pdev->dev);
+		dev_err(dev, "v4l2 init failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int mtk_fd_remove(struct platform_device *pdev)
+{
+	struct mtk_fd_dev *fd_dev = dev_get_drvdata(&pdev->dev);
+
+	mtk_fd_dev_v4l2_release(fd_dev);
+	mutex_destroy(&fd_dev->fd_hw.fd_hw_lock);
+	mutex_destroy(&fd_dev->vfd_lock);
+	pm_runtime_disable(&pdev->dev);
+
+	return 0;
+}
+
+static int mtk_fd_suspend(struct device *dev)
+{
+	struct mtk_fd_dev *fd_dev = dev_get_drvdata(dev);
+
+	if (pm_runtime_suspended(dev))
+		return 0;
+
+	/* suspend FD HW */
+	writel(0x0, fd_dev->fd_hw.fd_base + FD_HW_ENABLE);
+	writel(0x0, fd_dev->fd_hw.fd_base + FD_INT_EN);
+	clk_disable_unprepare(fd_dev->fd_hw.fd_clk);
+	return 0;
+}
+
+static int mtk_fd_resume(struct device *dev)
+{
+	struct mtk_fd_dev *fd_dev = dev_get_drvdata(dev);
+	int ret;
+
+	if (pm_runtime_suspended(dev))
+		return 0;
+
+	ret = clk_prepare_enable(fd_dev->fd_hw.fd_clk);
+	if (ret < 0) {
+		dev_dbg(dev, "open fd clk failed\n");
+		clk_disable_unprepare(fd_dev->fd_hw.fd_clk);
+	}
+
+	/* resume FD HW */
+	writel(ENABLE_FD, fd_dev->fd_hw.fd_base + FD_HW_ENABLE);
+	if (fd_dev->fd_hw.state == FD_ENQ)
+		writel(0x1, fd_dev->fd_hw.fd_base + FD_INT_EN);
+	return 0;
+}
+
+static const struct dev_pm_ops mtk_fd_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(mtk_fd_suspend, mtk_fd_resume)
+	SET_RUNTIME_PM_OPS(mtk_fd_suspend, mtk_fd_resume, NULL)
+};
+
+static const struct of_device_id mtk_fd_of_ids[] = {
+	{ .compatible = "mediatek,mt8183-fd", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, mtk_fd_of_ids);
+
+static struct platform_driver mtk_fd_driver = {
+	.probe   = mtk_fd_probe,
+	.remove  = mtk_fd_remove,
+	.driver  = {
+		.name  = "mtk-fd-4.0",
+		.of_match_table = of_match_ptr(mtk_fd_of_ids),
+		.pm = &mtk_fd_pm_ops,
+	}
+};
+module_platform_driver(mtk_fd_driver);
+
+MODULE_DESCRIPTION("Mediatek FD driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index 3dcfc61..eae876e 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -192,6 +192,10 @@ enum v4l2_colorfx {
  * We reserve 16 controls for this driver. */
 #define V4L2_CID_USER_IMX_BASE			(V4L2_CID_USER_BASE + 0x10b0)
 
+/* The base for the mediatek FD driver controls */
+/* We reserve 16 controls for this driver. */
+#define V4L2_CID_USER_MTK_FD_BASE		(V4L2_CID_USER_BASE + 0x10d0)
+
 /* MPEG-class control IDs */
 /* The MPEG controls are applicable to all codec controls
  * and the 'MPEG' part of the define is historical */
-- 
1.9.1

^ permalink raw reply related

* [RFC PATCH V2 3/4] media: platform: Add Mediatek FD driver KConfig
From: Jerry-ch Chen @ 2019-07-09  8:41 UTC (permalink / raw)
  To: hans.verkuil, laurent.pinchart+renesas, tfiga, matthias.bgg,
	mchehab
  Cc: shik, devicetree, Sean.Cheng, Rynn.Wu, srv_heupstream,
	po-yang.huang, suleiman, Jerry-ch Chen, jungo.lin, sj.huang,
	yuzhao, linux-mediatek, zwisler, christie.yu, frederic.chen,
	linux-arm-kernel, linux-media
In-Reply-To: <1562661672-22439-1-git-send-email-Jerry-Ch.chen@mediatek.com>

From: Jerry-ch Chen <jerry-ch.chen@mediatek.com>

This patch adds KConfig for Mediatek Face Detection driver (FD).
FD is embedded in Mediatek SoCs. It can provide hardware
accelerated face detection function.

Signed-off-by: Jerry-ch Chen <jerry-ch.chen@mediatek.com>
---
 drivers/media/platform/Kconfig            |  2 ++
 drivers/media/platform/mtk-isp/fd/Kconfig | 17 +++++++++++++++++
 2 files changed, 19 insertions(+)
 create mode 100644 drivers/media/platform/mtk-isp/fd/Kconfig

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index a505e9f..ae99258e 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -32,6 +32,8 @@ source "drivers/media/platform/davinci/Kconfig"
 
 source "drivers/media/platform/omap/Kconfig"
 
+source "drivers/media/platform/mtk-isp/fd/Kconfig"
+
 config VIDEO_ASPEED
 	tristate "Aspeed AST2400 and AST2500 Video Engine driver"
 	depends on VIDEO_V4L2
diff --git a/drivers/media/platform/mtk-isp/fd/Kconfig b/drivers/media/platform/mtk-isp/fd/Kconfig
new file mode 100644
index 0000000..0c5eaf0
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/fd/Kconfig
@@ -0,0 +1,17 @@
+config VIDEO_MEDIATEK_FD
+	bool "Mediatek face detection processing function"
+	select DMA_SHARED_BUFFER
+	select VIDEOBUF2_DMA_CONTIG
+	select VIDEOBUF2_CORE
+	select VIDEOBUF2_V4L2
+	select VIDEOBUF2_MEMOPS
+	select VIDEOBUF2_VMALLOC
+	select MEDIA_CONTROLLER
+
+	default n
+	help
+		Support the Face Detectioin (FD) feature.
+
+		FD driver is a V4L2 memory-to-memory device driver which
+		provides hardware accelerated face detection function,
+		it can detect different sizes of faces in a raw image.
-- 
1.9.1

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox