Devicetree
 help / color / mirror / Atom feed
* [PATCH v2 2/3] can: ti_hecc: Add TI HECC DT binding documentation
From: yegorslists @ 2017-01-11 14:05 UTC (permalink / raw)
  To: linux-can
  Cc: linux-omap, devicetree, robh+dt, jean-michel.hautbois,
	andrej.skvortzov, hs, Anton Glukhov, Yegor Yefremov
In-Reply-To: <1484143521-4898-1-git-send-email-yegorslists@googlemail.com>

From: Anton Glukhov <anton.a.glukhov@gmail.com>

DT binding documentation for TI High End CAN Controller

Signed-off-by: Anton Glukhov <anton.a.glukhov@gmail.com>
Signed-off-by: Yegor Yefremov <yegorslists@googlemail.com>
---
Changes v1 -> v2:
	change compatible to "ti,am3505"

 .../devicetree/bindings/net/can/ti_hecc.txt        | 31 ++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/can/ti_hecc.txt

diff --git a/Documentation/devicetree/bindings/net/can/ti_hecc.txt b/Documentation/devicetree/bindings/net/can/ti_hecc.txt
new file mode 100644
index 0000000..ce015cf
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/can/ti_hecc.txt
@@ -0,0 +1,31 @@
+* Texas Instruments High End CAN Controller (HECC)
+
+This file provides information, what the device node
+for the hecc interface contains.
+
+Required properties:
+- compatible: "ti,am3505"
+- reg: offset and length of the register set for the device
+- interrupts: interrupt mapping for the hecc interrupts sources
+- clocks: clock phandles (see clock bindings for details)
+- ti,scc-ram-offset: offset to scc module ram
+- ti,hecc-ram-offset: offset to hecc module ram
+- ti,mbx-offset: offset to mailbox ram
+
+Optional properties:
+- ti,int-line: interrupt line
+
+Example:
+
+For am3517evm board:
+	hecc: can@0x5c050000 {
+		compatible = "ti,am3505";
+		status = "disabled";
+		reg = <0x5c050000 0x4000>;
+		interrupts = <24>;
+		clocks = <&hecc_ck>;
+		ti,scc-ram-offset = <0x3000>;
+		ti,hecc-ram-offset = <0x3000>;
+		ti,mbx-offset = <0x2000>;
+		ti,int-line = <0>;
+	};
-- 
2.1.4


^ permalink raw reply related

* [PATCH v2 1/3] ARM: dts: AM35x: Add hecc node
From: yegorslists @ 2017-01-11 14:05 UTC (permalink / raw)
  To: linux-can
  Cc: linux-omap, devicetree, robh+dt, jean-michel.hautbois,
	andrej.skvortzov, hs, Anton Glukhov, Yegor Yefremov
In-Reply-To: <1484143521-4898-1-git-send-email-yegorslists@googlemail.com>

From: Anton Glukhov <anton.a.glukhov@gmail.com>

HECC node description for am35x SOCs

Signed-off-by: Anton Glukhov <anton.a.glukhov@gmail.com>
Signed-off-by: Yegor Yefremov <yegorslists@googlemail.com>
---
Changes v1 -> v2:
	change compatible to "ti,am3505"

 arch/arm/boot/dts/am3517.dtsi | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/arch/arm/boot/dts/am3517.dtsi b/arch/arm/boot/dts/am3517.dtsi
index 9fe545d..3bb8687 100644
--- a/arch/arm/boot/dts/am3517.dtsi
+++ b/arch/arm/boot/dts/am3517.dtsi
@@ -13,6 +13,7 @@
 / {
 	aliases {
 		serial3 = &uart4;
+		can = &hecc;
 	};
 
 	ocp@68000000 {
@@ -72,6 +73,18 @@
 			pinctrl-single,register-width = <16>;
 			pinctrl-single,function-mask = <0xff1f>;
 		};
+
+		hecc: can@5c050000 {
+			compatible = "ti,am3505";
+			status = "disabled";
+			reg = <0x5c050000 0x4000>;
+			interrupts = <24>;
+			clocks = <&hecc_ck>;
+			ti,scc-ram-offset = <0x3000>;
+			ti,hecc-ram-offset = <0x3000>;
+			ti,mbx-offset = <0x2000>;
+			ti,int-line = <0>;
+		};
 	};
 };
 
-- 
2.1.4


^ permalink raw reply related

* [PATCH v2 0/3] Adding DT support for TI HECC module
From: yegorslists-gM/Ye1E23mwN+BqQ9rBEUg @ 2017-01-11 14:05 UTC (permalink / raw)
  To: linux-can-u79uwXL29TY76Z2rM5mHXA
  Cc: linux-omap-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	jean-michel.hautbois-B+Q8N6RmIDZBDgjK7y7TUQ,
	andrej.skvortzov-Re5JQEeQqe8AvxtiuMwx3w, hs-ynQEQJNshbs,
	Yegor Yefremov

From: Yegor Yefremov <yegorslists-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org>

This is an attempt to revive DT support for TI HECC that was started in 2015.

I haven't changed much because not all questions could be fully answered:

* Should HECC use "am3505" as compatible?
* What should be done to the offsets (ti,scc-ram-offset, ti,hecc-ram-offset, ti,mbx-offset)?

Anton Glukhov (3):
  ARM: dts: AM35x: Add hecc node
  can: ti_hecc: Add TI HECC DT binding documentation
  can: ti_hecc: Add DT support for TI HECC module

 .../devicetree/bindings/net/can/ti_hecc.txt        | 31 +++++++++++++
 arch/arm/boot/dts/am3517.dtsi                      | 13 ++++++
 drivers/net/can/ti_hecc.c                          | 54 ++++++++++++++++++++--
 3 files changed, 94 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/net/can/ti_hecc.txt

-- 
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH v8 2/5] i2c: Add STM32F4 I2C driver
From: M'boumba Cedric Madianga @ 2017-01-11 13:58 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: Wolfram Sang, Rob Herring, Maxime Coquelin, Alexandre Torgue,
	Linus Walleij, Patrice Chotard, Russell King,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20170111082208.vzu7xgpd4eakyldl-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>

Hi Uwe,

2017-01-11 9:22 GMT+01:00 Uwe Kleine-König <u.kleine-koenig@pengutronix.de>:
> Hello Cedric,
>
> On Thu, Jan 05, 2017 at 10:07:23AM +0100, M'boumba Cedric Madianga wrote:
>> +/*
>> + * In standard mode:
>> + * SCL period = SCL high period = SCL low period = CCR * I2C parent clk period
>> + *
>> + * In fast mode:
>> + * If Duty = 0; SCL high period = 1  * CCR * I2C parent clk period
>> + *           SCL low period  = 2  * CCR * I2C parent clk period
>> + * If Duty = 1; SCL high period = 9  * CCR * I2C parent clk period
>> + *           SCL low period  = 16 * CCR * I2C parent clk period
> s/  \*/ */ several times

Sorry but I don't see where is the issue as the style for multi-line
comments seems ok.
Could you please clarify that point if possible ? Thanks in advance

>
>> + * In order to reach 400 kHz with lower I2C parent clk frequencies we always set
>> + * Duty = 1
>> + *
>> + * For both modes, we have CCR = SCL period * I2C parent clk frequency
>> + * with scl_period = 5 microseconds in Standard mode and scl_period = 1
> s/mode/Mode/

ok thanks

>
>> + * microsecond in Fast Mode in order to satisfy scl_high and scl_low periods
>> + * constraints defined by i2c bus specification
>
> I don't understand scl_period = 1 µs for Fast Mode. For a bus freqency
> of 400 kHz we need low + high = 2.5 µs. Is there a factor 10 missing
> somewhere?

As CCR = SCL_period * I2C parent clk frequency with minimal freq =
2Mhz and SCL_period = 1 we have:
CCR = 1 * 2Mhz = 2.
But to compute, scl_low and scl_high in Fast mode, we have to do the
following thing as Duty=1:
scl_high = 9 * CCR * I2C parent clk period
scl_low = 16 * CCR * I2C parent clk period
In our example:
scl_high = 9 * 2 * 0,0000005 = 0,000009 sec = 9 µs
scl_low = 16 * 2 * 0.0000005 = 0,000016 sec = 16 µs
So low + high = 27 µs > 2,5 µs

>
>> + */
>> +static struct stm32f4_i2c_timings i2c_timings[] = {
>> [...]
>> +
>> +/**
>> + * stm32f4_i2c_hw_config() - Prepare I2C block
>> + * @i2c_dev: Controller's private data
>> + */
>> +static int stm32f4_i2c_hw_config(struct stm32f4_i2c_dev *i2c_dev)
>> +{
>> +     void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR1;
>> +     int ret = 0;
>> +
>> +     /* Disable I2C */
>> +     stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR1_PE);
>> +
>> +     ret = stm32f4_i2c_set_periph_clk_freq(i2c_dev);
>> +     if (ret)
>> +             return ret;
>> +
>> +     stm32f4_i2c_set_rise_time(i2c_dev);
>> +
>> +     stm32f4_i2c_set_speed_mode(i2c_dev);
>> +
>> +     stm32f4_i2c_set_filter(i2c_dev);
>> +
>> +     /* Enable I2C */
>> +     stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_PE);
>
> This function is called after a hw reset, so there should be no need to
> use clr_bits and set_bits because the value read from hw should be
> known.

ok thanks

>
>> +     return ret;
>
> return 0;

ok thanks

>
>> +}
>> +
>> +static int stm32f4_i2c_wait_free_bus(struct stm32f4_i2c_dev *i2c_dev)
>> +{
>> +     u32 status;
>> +     int ret;
>> +
>> +     ret = readl_relaxed_poll_timeout(i2c_dev->base + STM32F4_I2C_SR2,
>> +                                      status,
>> +                                      !(status & STM32F4_I2C_SR2_BUSY),
>> +                                      10, 1000);
>> +     if (ret) {
>> +             dev_dbg(i2c_dev->dev, "bus not free\n");
>> +             ret = -EBUSY;
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +/**
>> + * stm32f4_i2c_write_ byte() - Write a byte in the data register
>> + * @i2c_dev: Controller's private data
>> + * @byte: Data to write in the register
>> + */
>> +static void stm32f4_i2c_write_byte(struct stm32f4_i2c_dev *i2c_dev, u8 byte)
>> +{
>> +     writel_relaxed(byte, i2c_dev->base + STM32F4_I2C_DR);
>> +}
>> +
>> +/**
>> + * stm32f4_i2c_write_msg() - Fill the data register in write mode
>> + * @i2c_dev: Controller's private data
>> + *
>> + * This function fills the data register with I2C transfer buffer
>> + */
>> +static void stm32f4_i2c_write_msg(struct stm32f4_i2c_dev *i2c_dev)
>> +{
>> +     struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
>> +
>> +     stm32f4_i2c_write_byte(i2c_dev, *msg->buf++);
>> +     msg->count--;
>> +}
>> +
>> +static void stm32f4_i2c_read_msg(struct stm32f4_i2c_dev *i2c_dev)
>> +{
>> +     struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
>> +     u32 rbuf;
>> +
>> +     rbuf = readl_relaxed(i2c_dev->base + STM32F4_I2C_DR);
>> +     *msg->buf++ = rbuf & 0xff;
>
> This is unnecessary. buf has an 8 bit wide type so
>
>         *msg->buf++ = rbuf;
>
> has the same effect. (ISTR this is something I already pointed out
> earlier?)

Yes you are right.

>
>> +     msg->count--;
>> +}
>> +
>> +static void stm32f4_i2c_terminate_xfer(struct stm32f4_i2c_dev *i2c_dev)
>> +{
>> +     struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
>> +     void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR2;
>> +
>> +     stm32f4_i2c_disable_irq(i2c_dev);
>> +
>> +     reg = i2c_dev->base + STM32F4_I2C_CR1;
>> +     if (msg->stop)
>> +             stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_STOP);
>> +     else
>> +             stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_START);
>> +
>> +     complete(&i2c_dev->complete);
>> +}
>> +
>> +/**
>> + * stm32f4_i2c_handle_write() - Handle FIFO empty interrupt in case of write
>> + * @i2c_dev: Controller's private data
>> + */
>> +static void stm32f4_i2c_handle_write(struct stm32f4_i2c_dev *i2c_dev)
>> +{
>> +     struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
>> +     void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR2;
>> +
>> +     if (msg->count) {
>> +             stm32f4_i2c_write_msg(i2c_dev);
>> +             if (!msg->count) {
>> +                     /* Disable buffer interrupts for RXNE/TXE events */
>> +                     stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR2_ITBUFEN);
>> +             }
>> +     } else {
>> +             stm32f4_i2c_terminate_xfer(i2c_dev);
>
> Is stm32f4_i2c_terminate_xfer also called when arbitration is lost? If
> yes, is it then right to set STM32F4_I2C_CR1_STOP or
> STM32F4_I2C_CR1_START?

If arbitration is lost, stm32f4_i2c_terminate_xfer() is not called.
In that case, we return -EAGAIN and i2c-core will retry by calling
stm32f4_i2c_xfer()

>
>> +     }
>> +}
>> +
>> +/**
>> + * stm32f4_i2c_handle_read() - Handle FIFO empty interrupt in case of read
>> + * @i2c_dev: Controller's private data
>> + */
>> +static void stm32f4_i2c_handle_read(struct stm32f4_i2c_dev *i2c_dev)
>> +{
>> +     struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
>> +     void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR2;
>> +
>> +     switch (msg->count) {
>> +     case 1:
>> +             stm32f4_i2c_disable_irq(i2c_dev);
>> +             stm32f4_i2c_read_msg(i2c_dev);
>> +             complete(&i2c_dev->complete);
>> +             break;
>> +     /*
>> +      * For 2 or 3-byte reception, we do not have to read the data register
>> +      * when RXNE occurs as we have to wait for byte transferred finished
>
> it's hard to understand because if you don't know the hardware the
> meaning of RXNE is unknown.

Ok I will replace RXNE by RX not empty in that comment

>
>> +      * event before reading data. So, here we just disable buffer
>> +      * interrupt in order to avoid another system preemption due to RXNE
>> +      * event
>> +      */
>> +     case 2:
>> +     case 3:
>> +             stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR2_ITBUFEN);
>> +             break;
>> +     /* For N byte reception with N > 3 we directly read data register */
>> +     default:
>> +             stm32f4_i2c_read_msg(i2c_dev);
>> +     }
>> +}
>> +
>> +/**
>> + * stm32f4_i2c_handle_rx_btf() - Handle byte transfer finished interrupt
>> + * in case of read
>> + * @i2c_dev: Controller's private data
>> + */
>> +static void stm32f4_i2c_handle_rx_btf(struct stm32f4_i2c_dev *i2c_dev)
>> +{
>
> btf is a hw-related name. Maybe better use _done which is easier to
> understand?

OK

>
>> +     struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
>> +     void __iomem *reg;
>> +     u32 mask;
>> +     int i;
>> +
>> +     switch (msg->count) {
>> +     case 2:
>> +             /*
>> +              * In order to correctly send the Stop or Repeated Start
>> +              * condition on the I2C bus, the STOP/START bit has to be set
>> +              * before reading the last two bytes.
>> +              * After that, we could read the last two bytes, disable
>> +              * remaining interrupts and notify the end of xfer to the
>> +              * client
>
> This is surprising. I didn't recheck the manual, but that looks very
> uncomfortable.

I agree but this exactly the hardware way of working described in the
reference manual.

>How does this work, when I only want to read a single
> byte? Same problem for ACK below.

For a single reception, we enable NACK and STOP or Repeatead START
bits during address match.
The NACK and STOP/START pulses are sent as soon as the data is
received in the shift register.
Please note that in that case, we don't have to wait BTF event to read the data.
Data is read as soon as RXNE event occurs.

>
>> +              */
>> +             reg = i2c_dev->base + STM32F4_I2C_CR1;
>> +             if (msg->stop)
>> +                     stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_STOP);
>> +             else
>> +                     stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_START);
>> +
>> +             for (i = 2; i > 0; i--)
>> +                     stm32f4_i2c_read_msg(i2c_dev);
>> +
>> +             reg = i2c_dev->base + STM32F4_I2C_CR2;
>> +             mask = STM32F4_I2C_CR2_ITEVTEN | STM32F4_I2C_CR2_ITERREN;
>> +             stm32f4_i2c_clr_bits(reg, mask);
>> +
>> +             complete(&i2c_dev->complete);
>> +             break;
>> +     case 3:
>> +             /*
>> +              * In order to correctly send the ACK on the I2C bus for the
>> +              * last two bytes, we have to set ACK bit before reading the
>> +              * third last data byte
>> +              */
>> +             reg = i2c_dev->base + STM32F4_I2C_CR1;
>> +             stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR1_ACK);
>> +             stm32f4_i2c_read_msg(i2c_dev);
>> +             break;
>> +     default:
>> +             stm32f4_i2c_read_msg(i2c_dev);
>> +     }
>> +}
>> +
>> +/**
>> + * stm32f4_i2c_handle_rx_addr() - Handle address matched interrupt in case of
>> + * master receiver
>> + * @i2c_dev: Controller's private data
>> + */
>> +static void stm32f4_i2c_handle_rx_addr(struct stm32f4_i2c_dev *i2c_dev)
>> +{
>> +     struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
>> +     void __iomem *reg;
>> +
>> +     switch (msg->count) {
>> +     case 0:
>> +             stm32f4_i2c_terminate_xfer(i2c_dev);
>> +             /* Clear ADDR flag */
>> +             readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2);
>> +             break;
>> +     case 1:
>> +             /*
>> +              * Single byte reception:
>
> This also happens for the last byte of a 5 byte transfer, right?

For a 5 byte transfer the behavior is different:
We have to read data from DR (data register)  as soon as the RXNE (RX
not empty) event occurs for data1, data2 and data3 (until N-2 data for
a more generic case)
The ACK is automatically sent as soon as the data is received in the
shift register as the I2C controller was configured to do that during
adress match phase.

For data3 (N-2 data), we wait for BTF (Byte Transfer finished) event
in order to set NACK before reading DR.
This event occurs when a new data has been received in shift register
(in our case data4 or N-1 data) but the prevoius data in DR (in our
case data3 or N-2 data) has not been read yet.
In that way, the NACK pulse will be correctly generated after the last
received data byte.

For data4 and data5, we wait for BTF event (data4 or N-1 data in DR
and data5 or N data in shift register), set STOP or repeated Start in
order to correctly sent the right pulse after the last received data
byte and run 2 consecutives read of DR.

>
>> +              * Enable NACK, clear ADDR flag and generate STOP or RepSTART
>> +              */
>> +             reg = i2c_dev->base + STM32F4_I2C_CR1;
>> +             stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR1_ACK);
>> +             readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2);
>> +             if (msg->stop)
>> +                     stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_STOP);
>> +             else
>> +                     stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_START);
>> +             break;
>> +     case 2:
>> +             /*
>> +              * 2-byte reception:
>> +              * Enable NACK and set POS
>
> What is POS?
POS is used to define the position of the (N)ACK pulse
0: ACK is generated when the current is being received in the shift register
1: ACK is generated when the next byte which will be received in the
shift register (used for 2-byte reception)

>
>> +              */
>> +             reg = i2c_dev->base + STM32F4_I2C_CR1;
>> +             stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR1_ACK);
>> +             stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_POS);
>
> You could get rid of this, when caching the value of CR1. Would save two
> register reads here. This doesn't work for all registers, but it should
> be possible to apply for most of them, maybe enough to get rid of the
> clr_bits and set_bits function.
>
>> +             readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2);
>> +             break;
>> +
>> +     default:
>> +             /* N-byte reception: Enable ACK */
>> +             reg = i2c_dev->base + STM32F4_I2C_CR1;
>> +             stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_ACK);
>
> Do you need to set ACK for each byte transferred?
I need to do that in order to be SMBus compatible and the ACK/NACK
seems to be used by default in Documentation/i2c/i2c-protocol file.

>
> I stopp reviewing here because of -ENOTIME on my side but don't want to
> delay discussion, so sent my comments up to here already now.
>
> Best regards
> Uwe
>
> --
> Pengutronix e.K.                           | Uwe Kleine-König            |
> Industrial Linux Solutions                 | http://www.pengutronix.de/  |
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH 0/3] arm64: dts: A64 board MMC support
From: Maxime Ripard @ 2017-01-11 13:51 UTC (permalink / raw)
  To: Andre Przywara
  Cc: Chen-Yu Tsai, Rob Herring, Mark Rutland,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1484011353-21480-1-git-send-email-andre.przywara-5wv7dgnIgG8@public.gmane.org>

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

Hi,

On Tue, Jan 10, 2017 at 01:22:30AM +0000, Andre Przywara wrote:
> These patches here go on top of Maxime's latest A64 MMC series and
> enable the MMC controllers on the boards using the A64 SoC.
> As the BananaPi-M64 DT now looks different, lets adds support for
> that board as well, with one major difference to the Pine64 being the eMMC
> chip.
> 
> I could't find commit f9ca9b952ee1 Maxime mentioned in his cover letter,
> so I applied his patches on top of sunxi/for-next and cherry-picked
> b4b8664d29 to fix the arm64 build.

I'm fine with all your patches. I've queued them in my MMC series, and
will continue pushing them and / or merging them if it's ok for you.

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

^ permalink raw reply

* [PATCHv5 3/8] rtc: add STM32 RTC driver
From: Amelie Delaunay @ 2017-01-11 13:46 UTC (permalink / raw)
  To: Alessandro Zummo, Alexandre Belloni, Rob Herring, Mark Rutland,
	Maxime Coquelin, Alexandre Torgue, Russell King
  Cc: rtc-linux-/JYPxA39Uh5TLH3MbocFFw,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Gabriel Fernandez,
	Amelie Delaunay

This patch adds support for the STM32 RTC.

Signed-off-by: Amelie Delaunay <amelie.delaunay-qxv4g6HH51o@public.gmane.org>
---
 drivers/rtc/Kconfig     |  11 +
 drivers/rtc/Makefile    |   1 +
 drivers/rtc/rtc-stm32.c | 727 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 739 insertions(+)
 create mode 100644 drivers/rtc/rtc-stm32.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index e859d14..11eb28a 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1706,6 +1706,17 @@ config RTC_DRV_PIC32
 	   This driver can also be built as a module. If so, the module
 	   will be called rtc-pic32
 
+config RTC_DRV_STM32
+	tristate "STM32 RTC"
+	select REGMAP_MMIO
+	depends on ARCH_STM32 || COMPILE_TEST
+	help
+	   If you say yes here you get support for the STM32 On-Chip
+	   Real Time Clock.
+
+	   This driver can also be built as a module, if so, the module
+	   will be called "rtc-stm32".
+
 comment "HID Sensor RTC drivers"
 
 config RTC_DRV_HID_SENSOR_TIME
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 1ac694a..87bd9cc 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -144,6 +144,7 @@ obj-$(CONFIG_RTC_DRV_SNVS)	+= rtc-snvs.o
 obj-$(CONFIG_RTC_DRV_SPEAR)	+= rtc-spear.o
 obj-$(CONFIG_RTC_DRV_STARFIRE)	+= rtc-starfire.o
 obj-$(CONFIG_RTC_DRV_STK17TA8)	+= rtc-stk17ta8.o
+obj-$(CONFIG_RTC_DRV_STM32) 	+= rtc-stm32.o
 obj-$(CONFIG_RTC_DRV_STMP)	+= rtc-stmp3xxx.o
 obj-$(CONFIG_RTC_DRV_ST_LPC)	+= rtc-st-lpc.o
 obj-$(CONFIG_RTC_DRV_SUN4V)	+= rtc-sun4v.o
diff --git a/drivers/rtc/rtc-stm32.c b/drivers/rtc/rtc-stm32.c
new file mode 100644
index 0000000..c4789b5
--- /dev/null
+++ b/drivers/rtc/rtc-stm32.c
@@ -0,0 +1,727 @@
+/*
+ * Copyright (C) Amelie Delaunay 2016
+ * Author:  Amelie Delaunay <amelie.delaunay-qxv4g6HH51o@public.gmane.org>
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#include <linux/bcd.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/ioport.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+
+#define DRIVER_NAME "stm32_rtc"
+
+/* STM32 RTC registers */
+#define STM32_RTC_TR		0x00
+#define STM32_RTC_DR		0x04
+#define STM32_RTC_CR		0x08
+#define STM32_RTC_ISR		0x0C
+#define STM32_RTC_PRER		0x10
+#define STM32_RTC_ALRMAR	0x1C
+#define STM32_RTC_WPR		0x24
+
+/* STM32_RTC_TR bit fields  */
+#define STM32_RTC_TR_SEC_SHIFT		0
+#define STM32_RTC_TR_SEC		GENMASK(6, 0)
+#define STM32_RTC_TR_MIN_SHIFT		8
+#define STM32_RTC_TR_MIN		GENMASK(14, 8)
+#define STM32_RTC_TR_HOUR_SHIFT		16
+#define STM32_RTC_TR_HOUR		GENMASK(21, 16)
+
+/* STM32_RTC_DR bit fields */
+#define STM32_RTC_DR_DATE_SHIFT		0
+#define STM32_RTC_DR_DATE		GENMASK(5, 0)
+#define STM32_RTC_DR_MONTH_SHIFT	8
+#define STM32_RTC_DR_MONTH		GENMASK(12, 8)
+#define STM32_RTC_DR_WDAY_SHIFT		13
+#define STM32_RTC_DR_WDAY		GENMASK(15, 13)
+#define STM32_RTC_DR_YEAR_SHIFT		16
+#define STM32_RTC_DR_YEAR		GENMASK(23, 16)
+
+/* STM32_RTC_CR bit fields */
+#define STM32_RTC_CR_FMT		BIT(6)
+#define STM32_RTC_CR_ALRAE		BIT(8)
+#define STM32_RTC_CR_ALRAIE		BIT(12)
+
+/* STM32_RTC_ISR bit fields */
+#define STM32_RTC_ISR_ALRAWF		BIT(0)
+#define STM32_RTC_ISR_INITS		BIT(4)
+#define STM32_RTC_ISR_RSF		BIT(5)
+#define STM32_RTC_ISR_INITF		BIT(6)
+#define STM32_RTC_ISR_INIT		BIT(7)
+#define STM32_RTC_ISR_ALRAF		BIT(8)
+
+/* STM32_RTC_PRER bit fields */
+#define STM32_RTC_PRER_PRED_S_SHIFT	0
+#define STM32_RTC_PRER_PRED_S		GENMASK(14, 0)
+#define STM32_RTC_PRER_PRED_A_SHIFT	16
+#define STM32_RTC_PRER_PRED_A		GENMASK(22, 16)
+
+/* STM32_RTC_ALRMAR and STM32_RTC_ALRMBR bit fields */
+#define STM32_RTC_ALRMXR_SEC_SHIFT	0
+#define STM32_RTC_ALRMXR_SEC		GENMASK(6, 0)
+#define STM32_RTC_ALRMXR_SEC_MASK	BIT(7)
+#define STM32_RTC_ALRMXR_MIN_SHIFT	8
+#define STM32_RTC_ALRMXR_MIN		GENMASK(14, 8)
+#define STM32_RTC_ALRMXR_MIN_MASK	BIT(15)
+#define STM32_RTC_ALRMXR_HOUR_SHIFT	16
+#define STM32_RTC_ALRMXR_HOUR		GENMASK(21, 16)
+#define STM32_RTC_ALRMXR_PM		BIT(22)
+#define STM32_RTC_ALRMXR_HOUR_MASK	BIT(23)
+#define STM32_RTC_ALRMXR_DATE_SHIFT	24
+#define STM32_RTC_ALRMXR_DATE		GENMASK(29, 24)
+#define STM32_RTC_ALRMXR_WDSEL		BIT(30)
+#define STM32_RTC_ALRMXR_WDAY_SHIFT	24
+#define STM32_RTC_ALRMXR_WDAY		GENMASK(27, 24)
+#define STM32_RTC_ALRMXR_DATE_MASK	BIT(31)
+
+/* STM32_RTC_WPR key constants */
+#define RTC_WPR_1ST_KEY			0xCA
+#define RTC_WPR_2ND_KEY			0x53
+#define RTC_WPR_WRONG_KEY		0xFF
+
+/*
+ * RTC registers are protected against parasitic write access.
+ * PWR_CR_DBP bit must be set to enable write access to RTC registers.
+ */
+/* STM32_PWR_CR */
+#define PWR_CR				0x00
+/* STM32_PWR_CR bit field */
+#define PWR_CR_DBP			BIT(8)
+
+struct stm32_rtc {
+	struct rtc_device *rtc_dev;
+	void __iomem *base;
+	struct regmap *dbp;
+	struct clk *ck_rtc;
+	int irq_alarm;
+};
+
+static void stm32_rtc_wpr_unlock(struct stm32_rtc *rtc)
+{
+	writel_relaxed(RTC_WPR_1ST_KEY, rtc->base + STM32_RTC_WPR);
+	writel_relaxed(RTC_WPR_2ND_KEY, rtc->base + STM32_RTC_WPR);
+}
+
+static void stm32_rtc_wpr_lock(struct stm32_rtc *rtc)
+{
+	writel_relaxed(RTC_WPR_WRONG_KEY, rtc->base + STM32_RTC_WPR);
+}
+
+static int stm32_rtc_enter_init_mode(struct stm32_rtc *rtc)
+{
+	unsigned int isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+
+	if (!(isr & STM32_RTC_ISR_INITF)) {
+		isr |= STM32_RTC_ISR_INIT;
+		writel_relaxed(isr, rtc->base + STM32_RTC_ISR);
+
+		/*
+		 * It takes around 2 ck_rtc clock cycles to enter in
+		 * initialization phase mode (and have INITF flag set). As
+		 * slowest ck_rtc frequency may be 32kHz and highest should be
+		 * 1MHz, we poll every 10 us with a timeout of 100ms.
+		 */
+		return readl_relaxed_poll_timeout_atomic(
+					rtc->base + STM32_RTC_ISR,
+					isr, (isr & STM32_RTC_ISR_INITF),
+					10, 100000);
+	}
+
+	return 0;
+}
+
+static void stm32_rtc_exit_init_mode(struct stm32_rtc *rtc)
+{
+	unsigned int isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+
+	isr &= ~STM32_RTC_ISR_INIT;
+	writel_relaxed(isr, rtc->base + STM32_RTC_ISR);
+}
+
+static int stm32_rtc_wait_sync(struct stm32_rtc *rtc)
+{
+	unsigned int isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+
+	isr &= ~STM32_RTC_ISR_RSF;
+	writel_relaxed(isr, rtc->base + STM32_RTC_ISR);
+
+	/*
+	 * Wait for RSF to be set to ensure the calendar registers are
+	 * synchronised, it takes around 2 ck_rtc clock cycles
+	 */
+	return readl_relaxed_poll_timeout_atomic(rtc->base + STM32_RTC_ISR,
+						 isr,
+						 (isr & STM32_RTC_ISR_RSF),
+						 10, 100000);
+}
+
+static irqreturn_t stm32_rtc_alarm_irq(int irq, void *dev_id)
+{
+	struct stm32_rtc *rtc = (struct stm32_rtc *)dev_id;
+	unsigned int isr, cr;
+
+	mutex_lock(&rtc->rtc_dev->ops_lock);
+
+	isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+
+	if ((isr & STM32_RTC_ISR_ALRAF) &&
+	    (cr & STM32_RTC_CR_ALRAIE)) {
+		/* Alarm A flag - Alarm interrupt */
+		dev_dbg(&rtc->rtc_dev->dev, "Alarm occurred\n");
+
+		/* Pass event to the kernel */
+		rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+		/* Clear event flag, otherwise new events won't be received */
+		writel_relaxed(isr & ~STM32_RTC_ISR_ALRAF,
+			       rtc->base + STM32_RTC_ISR);
+	}
+
+	mutex_unlock(&rtc->rtc_dev->ops_lock);
+
+	return IRQ_HANDLED;
+}
+
+/* Convert rtc_time structure from bin to bcd format */
+static void tm2bcd(struct rtc_time *tm)
+{
+	tm->tm_sec = bin2bcd(tm->tm_sec);
+	tm->tm_min = bin2bcd(tm->tm_min);
+	tm->tm_hour = bin2bcd(tm->tm_hour);
+
+	tm->tm_mday = bin2bcd(tm->tm_mday);
+	tm->tm_mon = bin2bcd(tm->tm_mon + 1);
+	tm->tm_year = bin2bcd(tm->tm_year - 100);
+	/*
+	 * Number of days since Sunday
+	 * - on kernel side, 0=Sunday...6=Saturday
+	 * - on rtc side, 0=invalid,1=Monday...7=Sunday
+	 */
+	tm->tm_wday = (!tm->tm_wday) ? 7 : tm->tm_wday;
+}
+
+/* Convert rtc_time structure from bcd to bin format */
+static void bcd2tm(struct rtc_time *tm)
+{
+	tm->tm_sec = bcd2bin(tm->tm_sec);
+	tm->tm_min = bcd2bin(tm->tm_min);
+	tm->tm_hour = bcd2bin(tm->tm_hour);
+
+	tm->tm_mday = bcd2bin(tm->tm_mday);
+	tm->tm_mon = bcd2bin(tm->tm_mon) - 1;
+	tm->tm_year = bcd2bin(tm->tm_year) + 100;
+	/*
+	 * Number of days since Sunday
+	 * - on kernel side, 0=Sunday...6=Saturday
+	 * - on rtc side, 0=invalid,1=Monday...7=Sunday
+	 */
+	tm->tm_wday %= 7;
+}
+
+static int stm32_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	unsigned int tr, dr;
+
+	/* Time and Date in BCD format */
+	tr = readl_relaxed(rtc->base + STM32_RTC_TR);
+	dr = readl_relaxed(rtc->base + STM32_RTC_DR);
+
+	tm->tm_sec = (tr & STM32_RTC_TR_SEC) >> STM32_RTC_TR_SEC_SHIFT;
+	tm->tm_min = (tr & STM32_RTC_TR_MIN) >> STM32_RTC_TR_MIN_SHIFT;
+	tm->tm_hour = (tr & STM32_RTC_TR_HOUR) >> STM32_RTC_TR_HOUR_SHIFT;
+
+	tm->tm_mday = (dr & STM32_RTC_DR_DATE) >> STM32_RTC_DR_DATE_SHIFT;
+	tm->tm_mon = (dr & STM32_RTC_DR_MONTH) >> STM32_RTC_DR_MONTH_SHIFT;
+	tm->tm_year = (dr & STM32_RTC_DR_YEAR) >> STM32_RTC_DR_YEAR_SHIFT;
+	tm->tm_wday = (dr & STM32_RTC_DR_WDAY) >> STM32_RTC_DR_WDAY_SHIFT;
+
+	/* We don't report tm_yday and tm_isdst */
+
+	bcd2tm(tm);
+
+	return 0;
+}
+
+static int stm32_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	unsigned int tr, dr;
+	int ret = 0;
+
+	tm2bcd(tm);
+
+	/* Time in BCD format */
+	tr = ((tm->tm_sec << STM32_RTC_TR_SEC_SHIFT) & STM32_RTC_TR_SEC) |
+	     ((tm->tm_min << STM32_RTC_TR_MIN_SHIFT) & STM32_RTC_TR_MIN) |
+	     ((tm->tm_hour << STM32_RTC_TR_HOUR_SHIFT) & STM32_RTC_TR_HOUR);
+
+	/* Date in BCD format */
+	dr = ((tm->tm_mday << STM32_RTC_DR_DATE_SHIFT) & STM32_RTC_DR_DATE) |
+	     ((tm->tm_mon << STM32_RTC_DR_MONTH_SHIFT) & STM32_RTC_DR_MONTH) |
+	     ((tm->tm_year << STM32_RTC_DR_YEAR_SHIFT) & STM32_RTC_DR_YEAR) |
+	     ((tm->tm_wday << STM32_RTC_DR_WDAY_SHIFT) & STM32_RTC_DR_WDAY);
+
+	stm32_rtc_wpr_unlock(rtc);
+
+	ret = stm32_rtc_enter_init_mode(rtc);
+	if (ret) {
+		dev_err(dev, "Can't enter in init mode. Set time aborted.\n");
+		goto end;
+	}
+
+	writel_relaxed(tr, rtc->base + STM32_RTC_TR);
+	writel_relaxed(dr, rtc->base + STM32_RTC_DR);
+
+	stm32_rtc_exit_init_mode(rtc);
+
+	ret = stm32_rtc_wait_sync(rtc);
+end:
+	stm32_rtc_wpr_lock(rtc);
+
+	return ret;
+}
+
+static int stm32_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	struct rtc_time *tm = &alrm->time;
+	unsigned int alrmar, cr, isr;
+
+	alrmar = readl_relaxed(rtc->base + STM32_RTC_ALRMAR);
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+	isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+
+	if (alrmar & STM32_RTC_ALRMXR_DATE_MASK) {
+		/*
+		 * Date/day doesn't matter in Alarm comparison so alarm
+		 * triggers every day
+		 */
+		tm->tm_mday = -1;
+		tm->tm_wday = -1;
+	} else {
+		if (alrmar & STM32_RTC_ALRMXR_WDSEL) {
+			/* Alarm is set to a day of week */
+			tm->tm_mday = -1;
+			tm->tm_wday = (alrmar & STM32_RTC_ALRMXR_WDAY) >>
+				      STM32_RTC_ALRMXR_WDAY_SHIFT;
+			tm->tm_wday %= 7;
+		} else {
+			/* Alarm is set to a day of month */
+			tm->tm_wday = -1;
+			tm->tm_mday = (alrmar & STM32_RTC_ALRMXR_DATE) >>
+				       STM32_RTC_ALRMXR_DATE_SHIFT;
+		}
+	}
+
+	if (alrmar & STM32_RTC_ALRMXR_HOUR_MASK) {
+		/* Hours don't matter in Alarm comparison */
+		tm->tm_hour = -1;
+	} else {
+		tm->tm_hour = (alrmar & STM32_RTC_ALRMXR_HOUR) >>
+			       STM32_RTC_ALRMXR_HOUR_SHIFT;
+		if (alrmar & STM32_RTC_ALRMXR_PM)
+			tm->tm_hour += 12;
+	}
+
+	if (alrmar & STM32_RTC_ALRMXR_MIN_MASK) {
+		/* Minutes don't matter in Alarm comparison */
+		tm->tm_min = -1;
+	} else {
+		tm->tm_min = (alrmar & STM32_RTC_ALRMXR_MIN) >>
+			      STM32_RTC_ALRMXR_MIN_SHIFT;
+	}
+
+	if (alrmar & STM32_RTC_ALRMXR_SEC_MASK) {
+		/* Seconds don't matter in Alarm comparison */
+		tm->tm_sec = -1;
+	} else {
+		tm->tm_sec = (alrmar & STM32_RTC_ALRMXR_SEC) >>
+			      STM32_RTC_ALRMXR_SEC_SHIFT;
+	}
+
+	bcd2tm(tm);
+
+	alrm->enabled = (cr & STM32_RTC_CR_ALRAE) ? 1 : 0;
+	alrm->pending = (isr & STM32_RTC_ISR_ALRAF) ? 1 : 0;
+
+	return 0;
+}
+
+static int stm32_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	unsigned int isr, cr;
+
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+
+	stm32_rtc_wpr_unlock(rtc);
+
+	/* We expose Alarm A to the kernel */
+	if (enabled)
+		cr |= (STM32_RTC_CR_ALRAIE | STM32_RTC_CR_ALRAE);
+	else
+		cr &= ~(STM32_RTC_CR_ALRAIE | STM32_RTC_CR_ALRAE);
+	writel_relaxed(cr, rtc->base + STM32_RTC_CR);
+
+	/* Clear event flag, otherwise new events won't be received */
+	isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+	isr &= ~STM32_RTC_ISR_ALRAF;
+	writel_relaxed(isr, rtc->base + STM32_RTC_ISR);
+
+	stm32_rtc_wpr_lock(rtc);
+
+	return 0;
+}
+
+static int stm32_rtc_valid_alrm(struct stm32_rtc *rtc, struct rtc_time *tm)
+{
+	unsigned int cur_day, cur_mon, cur_year, cur_hour, cur_min, cur_sec;
+	unsigned int dr = readl_relaxed(rtc->base + STM32_RTC_DR);
+	unsigned int tr = readl_relaxed(rtc->base + STM32_RTC_TR);
+
+	cur_day = (dr & STM32_RTC_DR_DATE) >> STM32_RTC_DR_DATE_SHIFT;
+	cur_mon = (dr & STM32_RTC_DR_MONTH) >> STM32_RTC_DR_MONTH_SHIFT;
+	cur_year = (dr & STM32_RTC_DR_YEAR) >> STM32_RTC_DR_YEAR_SHIFT;
+	cur_sec = (tr & STM32_RTC_TR_SEC) >> STM32_RTC_TR_SEC_SHIFT;
+	cur_min = (tr & STM32_RTC_TR_MIN) >> STM32_RTC_TR_MIN_SHIFT;
+	cur_hour = (tr & STM32_RTC_TR_HOUR) >> STM32_RTC_TR_HOUR_SHIFT;
+
+	/*
+	 * Assuming current date is M-D-Y H:M:S.
+	 * RTC alarm can't be set on a specific month and year.
+	 * So the valid alarm range is:
+	 *	M-D-Y H:M:S < alarm <= (M+1)-D-Y H:M:S
+	 * with a specific case for December...
+	 */
+	if ((((tm->tm_year > cur_year) &&
+	      (tm->tm_mon == 0x1) && (cur_mon == 0x12)) ||
+	     ((tm->tm_year == cur_year) &&
+	      (tm->tm_mon <= cur_mon + 1))) &&
+	    ((tm->tm_mday > cur_day) ||
+	     ((tm->tm_mday == cur_day) &&
+	     ((tm->tm_hour > cur_hour) ||
+	      ((tm->tm_hour == cur_hour) && (tm->tm_min > cur_min)) ||
+	      ((tm->tm_hour == cur_hour) && (tm->tm_min == cur_min) &&
+	       (tm->tm_sec >= cur_sec))))))
+		return 0;
+
+	return -EINVAL;
+}
+
+static int stm32_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	struct rtc_time *tm = &alrm->time;
+	unsigned int cr, isr, alrmar;
+	int ret = 0;
+
+	tm2bcd(tm);
+
+	/*
+	 * RTC alarm can't be set on a specific date, unless this date is
+	 * up to the same day of month next month.
+	 */
+	if (stm32_rtc_valid_alrm(rtc, tm) < 0) {
+		dev_err(dev, "Alarm can be set only on upcoming month.\n");
+		return -EINVAL;
+	}
+
+	alrmar = 0;
+	/* tm_year and tm_mon are not used because not supported by RTC */
+	alrmar |= (tm->tm_mday << STM32_RTC_ALRMXR_DATE_SHIFT) &
+		  STM32_RTC_ALRMXR_DATE;
+	/* 24-hour format */
+	alrmar &= ~STM32_RTC_ALRMXR_PM;
+	alrmar |= (tm->tm_hour << STM32_RTC_ALRMXR_HOUR_SHIFT) &
+		  STM32_RTC_ALRMXR_HOUR;
+	alrmar |= (tm->tm_min << STM32_RTC_ALRMXR_MIN_SHIFT) &
+		  STM32_RTC_ALRMXR_MIN;
+	alrmar |= (tm->tm_sec << STM32_RTC_ALRMXR_SEC_SHIFT) &
+		  STM32_RTC_ALRMXR_SEC;
+
+	stm32_rtc_wpr_unlock(rtc);
+
+	/* Disable Alarm */
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+	cr &= ~STM32_RTC_CR_ALRAE;
+	writel_relaxed(cr, rtc->base + STM32_RTC_CR);
+
+	/*
+	 * Poll Alarm write flag to be sure that Alarm update is allowed: it
+	 * takes around 2 ck_rtc clock cycles
+	 */
+	ret = readl_relaxed_poll_timeout_atomic(rtc->base + STM32_RTC_ISR,
+						isr,
+						(isr & STM32_RTC_ISR_ALRAWF),
+						10, 100000);
+
+	if (ret) {
+		dev_err(dev, "Alarm update not allowed\n");
+		goto end;
+	}
+
+	/* Write to Alarm register */
+	writel_relaxed(alrmar, rtc->base + STM32_RTC_ALRMAR);
+
+	if (alrm->enabled)
+		stm32_rtc_alarm_irq_enable(dev, 1);
+	else
+		stm32_rtc_alarm_irq_enable(dev, 0);
+
+end:
+	stm32_rtc_wpr_lock(rtc);
+
+	return ret;
+}
+
+static const struct rtc_class_ops stm32_rtc_ops = {
+	.read_time	= stm32_rtc_read_time,
+	.set_time	= stm32_rtc_set_time,
+	.read_alarm	= stm32_rtc_read_alarm,
+	.set_alarm	= stm32_rtc_set_alarm,
+	.alarm_irq_enable = stm32_rtc_alarm_irq_enable,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id stm32_rtc_of_match[] = {
+	{ .compatible = "st,stm32-rtc" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, stm32_rtc_of_match);
+#endif
+
+static int stm32_rtc_init(struct platform_device *pdev,
+			  struct stm32_rtc *rtc)
+{
+	unsigned int prer, pred_a, pred_s, pred_a_max, pred_s_max, cr;
+	unsigned int rate;
+	int ret = 0;
+
+	rate = clk_get_rate(rtc->ck_rtc);
+
+	/* Find prediv_a and prediv_s to obtain the 1Hz calendar clock */
+	pred_a_max = STM32_RTC_PRER_PRED_A >> STM32_RTC_PRER_PRED_A_SHIFT;
+	pred_s_max = STM32_RTC_PRER_PRED_S >> STM32_RTC_PRER_PRED_S_SHIFT;
+
+	for (pred_a = pred_a_max; pred_a >= 0; pred_a--) {
+		pred_s = (rate / (pred_a + 1)) - 1;
+
+		if (((pred_s + 1) * (pred_a + 1)) == rate)
+			break;
+	}
+
+	/*
+	 * Can't find a 1Hz, so give priority to RTC power consumption
+	 * by choosing the higher possible value for prediv_a
+	 */
+	if ((pred_s > pred_s_max) || (pred_a > pred_a_max)) {
+		pred_a = pred_a_max;
+		pred_s = (rate / (pred_a + 1)) - 1;
+
+		dev_warn(&pdev->dev, "ck_rtc is %s\n",
+			 (rate - ((pred_a + 1) * (pred_s + 1)) < 0) ?
+			 "fast" : "slow");
+	}
+
+	stm32_rtc_wpr_unlock(rtc);
+
+	ret = stm32_rtc_enter_init_mode(rtc);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"Can't enter in init mode. Prescaler config failed.\n");
+		goto end;
+	}
+
+	prer = (pred_s << STM32_RTC_PRER_PRED_S_SHIFT) & STM32_RTC_PRER_PRED_S;
+	writel_relaxed(prer, rtc->base + STM32_RTC_PRER);
+	prer |= (pred_a << STM32_RTC_PRER_PRED_A_SHIFT) & STM32_RTC_PRER_PRED_A;
+	writel_relaxed(prer, rtc->base + STM32_RTC_PRER);
+
+	/* Force 24h time format */
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+	cr &= ~STM32_RTC_CR_FMT;
+	writel_relaxed(cr, rtc->base + STM32_RTC_CR);
+
+	stm32_rtc_exit_init_mode(rtc);
+
+	ret = stm32_rtc_wait_sync(rtc);
+end:
+	stm32_rtc_wpr_lock(rtc);
+
+	return ret;
+}
+
+static int stm32_rtc_probe(struct platform_device *pdev)
+{
+	struct stm32_rtc *rtc;
+	struct resource *res;
+	int ret;
+
+	rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
+	if (!rtc)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	rtc->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(rtc->base))
+		return PTR_ERR(rtc->base);
+
+	rtc->dbp = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+						   "st,syscfg");
+	if (IS_ERR(rtc->dbp)) {
+		dev_err(&pdev->dev, "no st,syscfg\n");
+		return PTR_ERR(rtc->dbp);
+	}
+
+	rtc->ck_rtc = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(rtc->ck_rtc)) {
+		dev_err(&pdev->dev, "no ck_rtc clock");
+		return PTR_ERR(rtc->ck_rtc);
+	}
+
+	ret = clk_prepare_enable(rtc->ck_rtc);
+	if (ret)
+		return ret;
+
+	regmap_update_bits(rtc->dbp, PWR_CR, PWR_CR_DBP, PWR_CR_DBP);
+
+	/*
+	 * After a system reset, RTC_ISR.INITS flag can be read to check if
+	 * the calendar has been initalized or not. INITS flag is reset by a
+	 * power-on reset (no vbat, no power-supply). It is not reset if
+	 * ck_rtc parent clock has changed (so RTC prescalers need to be
+	 * changed). That's why we cannot rely on this flag to know if RTC
+	 * init has to be done.
+	 */
+	ret = stm32_rtc_init(pdev, rtc);
+	if (ret)
+		goto err;
+
+	rtc->irq_alarm = platform_get_irq(pdev, 0);
+	if (rtc->irq_alarm <= 0) {
+		dev_err(&pdev->dev, "no alarm irq\n");
+		ret = rtc->irq_alarm;
+		goto err;
+	}
+
+	platform_set_drvdata(pdev, rtc);
+
+	ret = device_init_wakeup(&pdev->dev, true);
+	if (ret)
+		dev_warn(&pdev->dev,
+			 "alarm won't be able to wake up the system");
+
+	rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name,
+			&stm32_rtc_ops, THIS_MODULE);
+	if (IS_ERR(rtc->rtc_dev)) {
+		ret = PTR_ERR(rtc->rtc_dev);
+		dev_err(&pdev->dev, "rtc device registration failed, err=%d\n",
+			ret);
+		goto err;
+	}
+
+	/* Handle RTC alarm interrupts */
+	ret = devm_request_threaded_irq(&pdev->dev, rtc->irq_alarm, NULL,
+					stm32_rtc_alarm_irq,
+					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+					pdev->name, rtc);
+	if (ret) {
+		dev_err(&pdev->dev, "IRQ%d (alarm interrupt) already claimed\n",
+			rtc->irq_alarm);
+		goto err;
+	}
+
+	/*
+	 * If INITS flag is reset (calendar year field set to 0x00), calendar
+	 * must be initialized
+	 */
+	if (!(readl_relaxed(rtc->base + STM32_RTC_ISR) & STM32_RTC_ISR_INITS))
+		dev_warn(&pdev->dev, "Date/Time must be initialized\n");
+
+	return 0;
+err:
+	clk_disable_unprepare(rtc->ck_rtc);
+
+	regmap_update_bits(rtc->dbp, PWR_CR, PWR_CR_DBP, ~PWR_CR_DBP);
+
+	device_init_wakeup(&pdev->dev, false);
+
+	return ret;
+}
+
+static int __exit stm32_rtc_remove(struct platform_device *pdev)
+{
+	struct stm32_rtc *rtc = platform_get_drvdata(pdev);
+	unsigned int cr;
+
+	/* Disable interrupts */
+	stm32_rtc_wpr_unlock(rtc);
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+	cr &= ~STM32_RTC_CR_ALRAIE;
+	writel_relaxed(cr, rtc->base + STM32_RTC_CR);
+	stm32_rtc_wpr_lock(rtc);
+
+	clk_disable_unprepare(rtc->ck_rtc);
+
+	/* Enable backup domain write protection */
+	regmap_update_bits(rtc->dbp, PWR_CR, PWR_CR_DBP, ~PWR_CR_DBP);
+
+	device_init_wakeup(&pdev->dev, false);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int stm32_rtc_suspend(struct device *dev)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+
+	if (device_may_wakeup(dev))
+		return enable_irq_wake(rtc->irq_alarm);
+
+	return 0;
+}
+
+static int stm32_rtc_resume(struct device *dev)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	int ret = 0;
+
+	ret = stm32_rtc_wait_sync(rtc);
+	if (ret < 0)
+		return ret;
+
+	if (device_may_wakeup(dev))
+		return disable_irq_wake(rtc->irq_alarm);
+
+	return ret;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(stm32_rtc_pm_ops,
+			 stm32_rtc_suspend, stm32_rtc_resume);
+
+static struct platform_driver stm32_rtc_driver = {
+	.probe		= stm32_rtc_probe,
+	.remove		= stm32_rtc_remove,
+	.driver		= {
+		.name	= DRIVER_NAME,
+		.pm	= &stm32_rtc_pm_ops,
+		.of_match_table = stm32_rtc_of_match,
+	},
+};
+
+module_platform_driver(stm32_rtc_driver);
+
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay-qxv4g6HH51o@public.gmane.org>");
+MODULE_DESCRIPTION("STMicroelectronics STM32 Real Time Clock driver");
+MODULE_LICENSE("GPL v2");
-- 
1.9.1

-- 
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
--- 
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply related

* Re: [PATCHv4 3/8] rtc: add STM32 RTC driver
From: Amelie DELAUNAY @ 2017-01-11 13:39 UTC (permalink / raw)
  To: Corentin Labbe
  Cc: Alessandro Zummo, Alexandre Belloni, Rob Herring, Mark Rutland,
	Maxime Coquelin, Alexandre TORGUE, Russell King,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	rtc-linux-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Gabriel FERNANDEZ,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
In-Reply-To: <20170111130438.GA9327@Red>

Hi Corentin,

Thanks for reviewing,

On 01/11/2017 02:04 PM, Corentin Labbe wrote:
> On Wed, Jan 11, 2017 at 01:48:25PM +0100, Amelie Delaunay wrote:
>> This patch adds support for the STM32 RTC.
>>
>> Signed-off-by: Amelie Delaunay <amelie.delaunay-qxv4g6HH51o@public.gmane.org>
>> ---
>>  drivers/rtc/Kconfig     |  11 +
>>  drivers/rtc/Makefile    |   1 +
>>  drivers/rtc/rtc-stm32.c | 727 ++++++++++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 739 insertions(+)
>>  create mode 100644 drivers/rtc/rtc-stm32.c
>
> [...]
>> +/* STM32_PWR_CR */
>> +#define PWR_CR				0x00
>> +/* STM32_PWR_CR bit field */
>> +#define PWR_CR_DBP			BIT(8)
>> +
>> +static struct regmap *dbp;
>
> Hello
>
> Why using a global static struct ?
> You could alloc a private structure in probe for storing it and use platform_set_drvdata()
>
This is to stay closer to how this backup domain protection is managed 
in clk-stm32f4 driver, but I realize that I haven't the same 
constraints. I'll move this struct in my stm32_rtc private structure.

> Regards
>

Regards

-- 
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
--- 
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply

* Re: [PATCH v2 6/6] arm64: allwinner: a64: Increase the MMC max frequency
From: Maxime Ripard @ 2017-01-11 13:38 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Ulf Hansson, Rob Herring, devicetree, linux-arm-kernel,
	linux-kernel, linux-mmc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Andre Przywara
In-Reply-To: <CAGb2v64vU5soFybaurCKu1HE9FNo=-NGiy33ZVeoR6E7m4kk6w-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>

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

Hi,

On Wed, Jan 11, 2017 at 02:44:00PM +0800, Chen-Yu Tsai wrote:
> On Wed, Jan 11, 2017 at 3:15 AM, Maxime Ripard
> <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> > Hi,
> >
> > On Tue, Jan 10, 2017 at 01:01:20AM +0800, Chen-Yu Tsai wrote:
> >> On Tue, Jan 10, 2017 at 12:46 AM, Maxime Ripard
> >> <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> >> > All the controllers can have a maximum frequency of 200MHz.
> >> >
> >> > Since older SoCs cannot go that high, we cannot change the default maximum
> >> > frequency, but fortunately for us we have a property for that in the DT.
> >> >
> >> > This also has the side effect of allowing to use the MMC HS200 mode for the
> >> > boards that support it (with either 1.2v or 1.8v IOs).
> >> >
> >> > Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> >> > ---
> >> >  arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi | 3 +++
> >> >  1 file changed, 3 insertions(+), 0 deletions(-)
> >> >
> >> > diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
> >> > index 8e149498e096..f46ae965cf5b 100644
> >> > --- a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
> >> > +++ b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
> >> > @@ -332,6 +332,7 @@
> >> >                         resets = <&ccu RST_BUS_MMC0>;
> >> >                         reset-names = "ahb";
> >> >                         interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
> >> > +                       max-frequency = <200000000>;
> >>
> >> You also have to set one of MMC_CAP2_HS200* in the driver,
> >> or mmc-hs200-1_8v or mmc-hs200-1_2v in the device tree to
> >> actually use HS200, right?
> >
> > Yes, but that requires a board with 1.8V IOs to work properly, which
> > not all board use, so it's probably best to enable it in the board
> > DTS.
> 
> It's limited by the vqmmc regulator. Either way the host controller
> supports it right?

Yes, but if the card supports HS200 with 1.8V, and the controller
reports it too, the core will pick that mode and will try to switch to
it, which in turn will fail, making the card initialisation fail as
well.

We basically have two choices: either we ask all the boards that
support it to set mmc-hs200-1_8v in their DTS, or we set it in the
DTSI and we have the boards set no-1-8-v in their DTS if they do not
support those modes.

The first case is just the more convenient, because so far we've only
seen one non-upstream (yet) board that supports 1.8V IOs. All the
other do not.

Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

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

^ permalink raw reply

* Re: [PATCH 1/3] ARM: at91: flush the L2 cache before entering cpu idle
From: Jean-Jacques Hiblot @ 2017-01-11 13:18 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Mark Rutland, devicetree, Wenyou Yang, Nicolas Ferre,
	Linux Kernel Mailing List, robh+dt, Alexandre Belloni,
	linux-arm-kernel@lists.infradead.org
In-Reply-To: <20170111111814.GJ14217@n2100.armlinux.org.uk>

2017-01-11 12:18 GMT+01:00 Russell King - ARM Linux <linux@armlinux.org.uk>:
> On Wed, Jan 11, 2017 at 12:05:05PM +0100, Jean-Jacques Hiblot wrote:
>> 2017-01-11 9:15 GMT+01:00  <Wenyou.Yang@microchip.com>:
>> > Hi Jean-Jacques,
>> >
>> >> -----Original Message-----
>> >> From: Jean-Jacques Hiblot [mailto:jjhiblot@gmail.com]
>> >> Sent: 2017年1月11日 0:51
>> >> To: Alexandre Belloni <alexandre.belloni@free-electrons.com>
>> >> Cc: Wenyou Yang - A41535 <Wenyou.Yang@microchip.com>; Mark Rutland
>> >> <mark.rutland@arm.com>; devicetree <devicetree@vger.kernel.org>; Russell
>> >> King <linux@arm.linux.org.uk>; Wenyou Yang - A41535
>> >> <Wenyou.Yang@microchip.com>; Nicolas Ferre <nicolas.ferre@atmel.com>;
>> >> Linux Kernel Mailing List <linux-kernel@vger.kernel.org>; Rob Herring
>> >> <robh+dt@kernel.org>; linux-arm-kernel@lists.infradead.org
>> >> Subject: Re: [PATCH 1/3] ARM: at91: flush the L2 cache before entering cpu idle
>> >>
>> >> 2017-01-10 17:18 GMT+01:00 Alexandre Belloni
>> >> <alexandre.belloni@free-electrons.com>:
>> >> > I though a bit more about it, and I don't really like the new
>> >> > compatible string. I don't feel this should be necessary.
>> >> >
>> >> > What about the following:
>> >> >
>> >> > diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c index
>> >> > b4332b727e9c..0333aca63e44 100644
>> >> > --- a/arch/arm/mach-at91/pm.c
>> >> > +++ b/arch/arm/mach-at91/pm.c
>> >> > @@ -53,6 +53,7 @@ extern void at91_pinctrl_gpio_resume(void);  static
>> >> > struct {
>> >> >         unsigned long uhp_udp_mask;
>> >> >         int memctrl;
>> >> > +       bool has_l2_cache;
>> >> >  } at91_pm_data;
>> >> >
>> >> >  void __iomem *at91_ramc_base[2];
>> >> > @@ -267,6 +268,11 @@ static void at91_ddr_standby(void)
>> >> >         u32 lpr0, lpr1 = 0;
>> >> >         u32 saved_lpr0, saved_lpr1 = 0;
>> >> >
>> >>
>> >> > +       if (at91_pm_data.has_l2_cache) {
>> >> > +               flush_cache_all();
>> >> what is the point of calling flush_cache_all() here ? Do we really care that dirty
>> >> data in L1 is written to DDR ? I may be missing something but to me it's just extra
>> >> latency.
>> >
>> > Are you mean use outer_flush_all() to flush all cache lines in the outer cache only?
>>
>> Yes that's what I meant. You see, you don't flush the cache for
>> sama5d3 so it shouldn't be required either for sam5d4. You should be
>> able to test it quickly and see if L1 flush is indeed required by
>> replacing  flush_cache_all() with outer_flush_all(). BTW is highly
>> probable that L2 cache flush is done in outer_disable() so calling
>> outer_flush_all() is probably no required.
>
> Please don't.  Read the comments in the code, and understand the APIs
> that you're suggesting people use _before_ making the suggestion:
>
> /**
>  * outer_flush_all - clean and invalidate all cache lines in the outer cache
>  *
>  * Note: depending on implementation, this may not be atomic - it must
>  * only be called with interrupts disabled and no other active outer
>  * cache masters.
>  *
>  * It is intended that this function is only used by implementations
>  * needing to override the outer_cache.disable() method due to security.
>  * (Some implementations perform this as a clean followed by an invalidate.)
>  */
>
> So, outer_flush_all() should not be called except from L2 cache code
> implementing the outer_disable() function - it's not intended for
> platforms to use.

OK. My bad. I didn't understand the comments.

>
> There are, however, sadly three users of outer_flush_all() which have
> crept in through arm-soc, that should be outer_disable() instead.
>
> --
> RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
> FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
> according to speedtest.net.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCHv4 3/8] rtc: add STM32 RTC driver
From: Corentin Labbe @ 2017-01-11 13:04 UTC (permalink / raw)
  To: Amelie Delaunay
  Cc: Alessandro Zummo, Alexandre Belloni, Rob Herring, Mark Rutland,
	Maxime Coquelin, Alexandre Torgue, Russell King,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	rtc-linux-/JYPxA39Uh5TLH3MbocFFw,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Gabriel Fernandez,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1484138905-5903-1-git-send-email-amelie.delaunay-qxv4g6HH51o@public.gmane.org>

On Wed, Jan 11, 2017 at 01:48:25PM +0100, Amelie Delaunay wrote:
> This patch adds support for the STM32 RTC.
> 
> Signed-off-by: Amelie Delaunay <amelie.delaunay-qxv4g6HH51o@public.gmane.org>
> ---
>  drivers/rtc/Kconfig     |  11 +
>  drivers/rtc/Makefile    |   1 +
>  drivers/rtc/rtc-stm32.c | 727 ++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 739 insertions(+)
>  create mode 100644 drivers/rtc/rtc-stm32.c

[...]
> +/* STM32_PWR_CR */
> +#define PWR_CR				0x00
> +/* STM32_PWR_CR bit field */
> +#define PWR_CR_DBP			BIT(8)
> +
> +static struct regmap *dbp;

Hello

Why using a global static struct ?
You could alloc a private structure in probe for storing it and use platform_set_drvdata()

Regards

-- 
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
--- 
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply

* Re: [PATCH v1 1/1] iio: adc: tlc4541: add support for TI tlc4541 adc
From: Phil Reid @ 2017-01-11 12:53 UTC (permalink / raw)
  To: Peter Meerwald-Stadler
  Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A, knaack.h-Mmb7MZpHnFY,
	lars-Qo5EllUWu/uELgA04lAiVw, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	mark.rutland-5wv7dgnIgG8, linux-iio-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <alpine.DEB.2.02.1701111007240.12204-jW+XmwGofnusTnJN9+BGXg@public.gmane.org>

G'day Peter,

Thanks for reviewing.

On 11/01/2017 17:17, Peter Meerwald-Stadler wrote:
> On Wed, 11 Jan 2017, Phil Reid wrote:
>
>> Oops, title should be PATCH V2.
>>
>> On 11/01/2017 14:51, Phil Reid wrote:
>>> This adds TI's tlc4541 16-bit ADC driver. Which is a single channel
>>> ADC. Supports raw and trigger buffer access.
>>> Also supports the tlc3541 14-bit device, which has not been tested.
>>> Implementation of the tlc3541 is fairly straight forward thou.
>
> comments below
>
>>
>>> Signed-off-by: Phil Reid <preid-qgqNFa1JUf/o2iN0hyhwsIdd74u8MsAO@public.gmane.org>
>>> ---
>>>
>>> Notes:
>>>     Changes from v1:
>>>     - Add tlc3541 support and chan spec.
>>>     - remove fields that where already 0 from TLC4541_V_CHAN macro
>>>     - Increase rx_buf size in tlc4541_state to avoid copy in
>>> tlc4541_trigger_handle
>>>     - Remove erroneous be16_to_cpu in tlc4541_trigger_handle
>>>     - Docs/binding: spi -> SPI & add ti,tlc3541
>>>
>>>     I haven't add Rob's Ack due to adding a new compatible string.
>>>
>>>     I tried to ".index = 1" from the spec as suggested by Peter, but that
>>> didn't
>>>     seem to work. Perhaps remove of .channel was the intended target.
>
> the only between index = 0/1 should be that the channel is called
> in_voltage0_raw vs in_voltage_raw in sysfs -- maybe there is an issue in
> iio_readdev?
I'll write something tomorrow to test this a bit more using libiio.

I did also notice iio_info reported the channel differently as well.
 From memory:
Without indexed it didn't output the format the same either.
  (input, index: 0, format: be:U16/16>>0)
became just
  (input)

I'll have to check what version of libiio I'm using as well.

>
>>>     Example output from iio_readdev
>>>
>>>     with ".index = 1"
>>>     root@cyclone5:~# mkdir /sys/kernel/config/iio/triggers/hrtimer/hr1
>>>     root@cyclone5:~# iio_readdev -t hr1 -b 32 -s 10 tlc4541 | hexdump
>>>     WARNING: High-speed mode not enabled
>>>     0000000 af00 0000 0000 0000 b922 ca99 93da 1492
>>>     0000010 a800 00ff 0000 0000 b246 cb30 93da 1492
>>>     0000020 a900 0000 0000 0000 4f9c cbc9 93da 1492
>>>     0000030 aa00 00ff 0000 0000 bd2c cc61 93da 1492
>>>     0000040 aa00 00ff 0000 0000 544c ccfa 93da 1492
>>>     0000050 ab00 00ff 0000 0000 e806 cd92 93da 1492
>>>     0000060 a900 00ff 0000 0000 846c ce2b 93da 1492
>>>     0000070 ab00 0000 0000 0000 2efc cec8 93da 1492
>>>     0000080 a800 00ff 0000 0000 b090 cf5c 93da 1492
>>>     0000090 a900 00ff 0000 0000 476a cff5 93da 1492
>>>
>>>     without .index
>>>     root@cyclone5:~# mkdir /sys/kernel/config/iio/triggers/hrtimer/hr1
>>>     root@cyclone5:~# iio_readdev -t hr1 -b 32 -s 10 tlc4541 | hexdump
>>>     WARNING: High-speed mode not enabled
>>>     0000000 6db0 eeb6 93e3 1492 35e0 ef4f 93e3 1492
>>>     0000010 4b34 efe5 93e3 1492 e9f2 f07d 93e3 1492
>>>     0000020 6182 f116 93e3 1492 090a f1af 93e3 1492
>>>     0000030 409c f249 93e3 1492 6c1a f2e0 93e3 1492
>>>     0000040 cd02 f378 93e3 1492 9582 f411 93e3 1492
>>>
>>>  .../devicetree/bindings/iio/adc/ti-tlc4541.txt     |  17 ++
>>>  drivers/iio/adc/Kconfig                            |  11 +
>>>  drivers/iio/adc/Makefile                           |   1 +
>>>  drivers/iio/adc/ti-tlc4541.c                       | 276
>>> +++++++++++++++++++++
>>>  4 files changed, 305 insertions(+)
>>>  create mode 100644 Documentation/devicetree/bindings/iio/adc/ti-tlc4541.txt
>>>  create mode 100644 drivers/iio/adc/ti-tlc4541.c
>>>
>>> diff --git a/Documentation/devicetree/bindings/iio/adc/ti-tlc4541.txt
>>> b/Documentation/devicetree/bindings/iio/adc/ti-tlc4541.txt
>>> new file mode 100644
>>> index 0000000..e1de2bd
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/iio/adc/ti-tlc4541.txt
>>> @@ -0,0 +1,17 @@
>>> +* Texas Instruments' TLC4541
>>> +
>>> +Required properties:
>>> + - compatible: Should be one of
>>> +	* "ti,tlc4541"
>>> +	* "ti,tlc3541"
>>> +	- reg: SPI chip select number for the device
>>> + - vref-supply: The regulator supply for ADC reference voltage
>>> + - spi-max-frequency: Max SPI frequency to use (<= 200000)
>>> +
>>> +Example:
>>> +adc@0 {
>>> +	compatible = "ti,adc0832";
>
> pasto here, should be ti,tlc4541 probably
yes

>
>>> +	reg = <0>;
>>> +	vref-supply = <&vdd_supply>;
>>> +	spi-max-frequency = <200000>;
>>> +};
>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>> index 99c0514..4dda3f0 100644
>>> --- a/drivers/iio/adc/Kconfig
>>> +++ b/drivers/iio/adc/Kconfig
>>> @@ -525,6 +525,17 @@ config TI_AM335X_ADC
>>>  	  To compile this driver as a module, choose M here: the module will
>>> be
>>>  	  called ti_am335x_adc.
>>>
>>> +config TI_TLC4541
>>> +	tristate "Texas Instruments TLC4541 ADC driver"
>>> +	depends on SPI
>>> +	select IIO_BUFFER
>>> +	select IIO_TRIGGERED_BUFFER
>>> +	help
>>> +	  Say yes here to build support for Texas Instruments TLC4541 ADC
>
> mention TLC3541 here as well?
ok
>
>>> chip.
>>> +
>>> +	  This driver can also be built as a module. If so, the module will be
>>> +	  called ti-tlc4541.
>>> +
>>>  config TWL4030_MADC
>>>  	tristate "TWL4030 MADC (Monitoring A/D Converter)"
>>>  	depends on TWL4030_CORE
>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>> index 7a40c04..9bf2377 100644
>>> --- a/drivers/iio/adc/Makefile
>>> +++ b/drivers/iio/adc/Makefile
>>> @@ -49,6 +49,7 @@ obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o
>>>  obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o
>>>  obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o
>>>  obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
>>> +obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o
>>>  obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
>>>  obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
>>>  obj-$(CONFIG_VF610_ADC) += vf610_adc.o
>>> diff --git a/drivers/iio/adc/ti-tlc4541.c b/drivers/iio/adc/ti-tlc4541.c
>>> new file mode 100644
>>> index 0000000..a0cd5e1
>>> --- /dev/null
>>> +++ b/drivers/iio/adc/ti-tlc4541.c
>>> @@ -0,0 +1,276 @@
>>> +/*
>>> + * TI tlc4541 ADC Driver
>>> + *
>>> + * Copyright (C) 2017 Phil Reid
>>> + *
>>> + * Datasheets can be found here:
>>> + * http://www.ti.com/lit/gpn/tlc3541
>>> + * http://www.ti.com/lit/gpn/tlc4541
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License version 2 as
>>> + * published by the Free Software Foundation.
>>> + *
>>> + * The tlc4541 requires 24 clock cycles to start a transfer.
>>> + * Conversion then takes 2.94us to complete before data is ready
>>> + * Data is returned MSB first.
>>> + */
>>> +
>>> +#include <linux/delay.h>
>>> +#include <linux/device.h>
>>> +#include <linux/err.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/iio/iio.h>
>>> +#include <linux/iio/sysfs.h>
>>> +#include <linux/iio/buffer.h>
>>> +#include <linux/iio/trigger_consumer.h>
>>> +#include <linux/iio/triggered_buffer.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/spi/spi.h>
>>> +#include <linux/sysfs.h>
>>> +
>>> +struct tlc4541_state {
>>> +	struct spi_device               *spi;
>>> +	struct regulator                *reg;
>>> +	struct spi_transfer             scan_single_xfer[3];
>>> +	struct spi_message              scan_single_msg;
>>> +
>>> +	/*
>>> +	 * DMA (thus cache coherency maintenance) requires the
>>> +	 * transfer buffers to live in their own cache lines.
>>> +	 * 2 bytes data + 6 bytes padding + 8 bytes timestamp when
>>> +	 * call iio_push_to_buffers_with_timestamp.
>>> +	 */
>>> +	__be16                          rx_buf[8] ____cacheline_aligned;
>>> +};
>>> +
>>> +struct tlc4541_chip_info {
>>> +	const struct iio_chan_spec *channels;
>>> +	unsigned int num_channels;
>>> +};
>>> +
>>> +enum tlc4541_id {
>>> +	TLC3541,
>>> +	TLC4541,
>>> +};
>>> +
>>> +#define TLC4541_V_CHAN(bits, bitshift) {                              \
>>> +		.type = IIO_VOLTAGE,                                  \
>>> +		.indexed = 1,                                         \
>
> shouldn't be needed
>
>>> +		.info_mask_separate       = BIT(IIO_CHAN_INFO_RAW),   \
>>> +		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>>> +		.scan_type = {                                        \
>>> +			.sign = 'u',                                  \
>>> +			.realbits = (bits),                           \
>>> +			.storagebits = 16,                            \
>>> +			.shift = bitshift,                            \
>
> (bitshift)
ok
>
>>> +			.endianness = IIO_BE,                         \
>>> +		},                                                    \
>>> +	}
>>> +
>>> +#define DECLARE_TLC4541_CHANNELS(name, bits, bitshift) \
>>> +const struct iio_chan_spec name ## _channels[] = { \
>>> +	TLC4541_V_CHAN(bits, bitshift), \
>>> +	IIO_CHAN_SOFT_TIMESTAMP(1), \
>>> +}
>>> +
>>> +static DECLARE_TLC4541_CHANNELS(tlc4541, 16, 0);
>>> +static DECLARE_TLC4541_CHANNELS(tlc3541, 14, 2);
>
> maybe always keep the chip variants in the same order, the enum has 3541
> first
ok
>
>>> +
>>> +static const struct tlc4541_chip_info tlc4541_chip_info[] = {
>>> +	[TLC4541] = {
>>> +		.channels = tlc4541_channels,
>>> +		.num_channels = ARRAY_SIZE(tlc4541_channels),
>>> +	},
>>> +	[TLC3541] = {
>>> +		.channels = tlc3541_channels,
>>> +		.num_channels = ARRAY_SIZE(tlc3541_channels),
>>> +	},
>>> +};
>>> +
>>> +static irqreturn_t tlc4541_trigger_handler(int irq, void *p)
>>> +{
>>> +	struct iio_poll_func *pf = p;
>>> +	struct iio_dev *indio_dev = pf->indio_dev;
>>> +	struct tlc4541_state *st = iio_priv(indio_dev);
>>> +	int ret;
>>> +
>>> +	ret = spi_sync(st->spi, &st->scan_single_msg);
>>> +	if (ret < 0)
>>> +		goto done;
>>> +
>>> +	iio_push_to_buffers_with_timestamp(indio_dev, st->rx_buf,
>>> +					   iio_get_time_ns(indio_dev));
>>> +
>>> +done:
>>> +	iio_trigger_notify_done(indio_dev->trig);
>>> +	return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int tlc4541_get_range(struct tlc4541_state *st)
>>> +{
>>> +	int vref;
>>> +
>>> +	vref = regulator_get_voltage(st->reg);
>>> +	if (vref < 0)
>>> +		return vref;
>>> +
>>> +	vref /= 1000;
>>> +
>>> +	return vref;
>>> +}
>>> +
>>> +static int tlc4541_read_raw(struct iio_dev *indio_dev,
>>> +			    struct iio_chan_spec const *chan,
>>> +			    int *val,
>>> +			    int *val2,
>>> +			    long m)
>>> +{
>>> +	int ret = 0;
>>> +	struct tlc4541_state *st = iio_priv(indio_dev);
>>> +
>>> +	switch (m) {
>>> +	case IIO_CHAN_INFO_RAW:
>>> +		ret = iio_device_claim_direct_mode(indio_dev);
>>> +		if (ret)
>>> +			return ret;
>>> +		ret = spi_sync(st->spi, &st->scan_single_msg);
>>> +		iio_device_release_direct_mode(indio_dev);
>>> +		if (ret < 0)
>>> +			return ret;
>>> +		*val = be16_to_cpu(st->rx_buf[0]);
>>> +		*val = *val >> chan->scan_type.shift;
>>> +		*val &= GENMASK(chan->scan_type.realbits - 1, 0);
>
> is the GENMASK() necessary?, the trigger handler doesn't do it
Copied behaviour from another driver. eg ad7887.c

Looking at iio_channel_convert in libiio I think it looks to be doing the same
thing to the data from the trigger handle.


>
>>> +		return IIO_VAL_INT;
>>> +	case IIO_CHAN_INFO_SCALE:
>>> +		ret = tlc4541_get_range(st);
>>> +		if (ret < 0)
>>> +			return ret;
>>> +		*val = ret;
>>> +		*val2 = chan->scan_type.realbits;
>>> +		return IIO_VAL_FRACTIONAL_LOG2;
>>> +	}
>>> +	return -EINVAL;
>>> +}
>>> +
>>> +static const struct iio_info tlc4541_info = {
>>> +	.read_raw = &tlc4541_read_raw,
>>> +	.driver_module = THIS_MODULE,
>>> +};
>>> +
>>> +static int tlc4541_probe(struct spi_device *spi)
>>> +{
>>> +	struct tlc4541_state *st;
>>> +	struct iio_dev *indio_dev;
>>> +	const struct tlc4541_chip_info *info;
>>> +	int ret;
>>> +	int8_t device_init = 0;
>>> +
>>> +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
>
> wondering what happens to the cache aligned rx_buf here?
I don't know I just copied cache align from one of the other iio drivers.
There's multiple hits with the ____cacheline_aligned keyword.

>
>>> +	if (indio_dev == NULL)
>>> +		return -ENOMEM;
>>> +
>>> +	st = iio_priv(indio_dev);
>>> +
>>> +	spi_set_drvdata(spi, indio_dev);
>>> +
>>> +	st->spi = spi;
>>> +
>>> +	info = &tlc4541_chip_info[spi_get_device_id(spi)->driver_data];
>>> +
>>> +	indio_dev->name = spi_get_device_id(spi)->name;
>>> +	indio_dev->dev.parent = &spi->dev;
>>> +	indio_dev->modes = INDIO_DIRECT_MODE;
>>> +	indio_dev->channels = info->channels;
>>> +	indio_dev->num_channels = info->num_channels;
>>> +	indio_dev->info = &tlc4541_info;
>>> +
>>> +	/* perform reset */
>>> +	spi_write(spi, &device_init, 1);
>>> +
>>> +	/* Setup default message */
>>> +	st->scan_single_xfer[0].rx_buf = &st->rx_buf[0];
>>> +	st->scan_single_xfer[0].len = 3;
>>> +	st->scan_single_xfer[1].delay_usecs = 3;
>>> +	st->scan_single_xfer[2].rx_buf = &st->rx_buf[0];
>>> +	st->scan_single_xfer[2].len = 2;
>>> +
>>> +	spi_message_init(&st->scan_single_msg);
>>> +	spi_message_add_tail(&st->scan_single_xfer[0], &st->scan_single_msg);
>>> +	spi_message_add_tail(&st->scan_single_xfer[1], &st->scan_single_msg);
>>> +	spi_message_add_tail(&st->scan_single_xfer[2], &st->scan_single_msg);
>>> +
>>> +	st->reg = devm_regulator_get(&spi->dev, "vref");
>>> +	if (IS_ERR(st->reg))
>>> +		return PTR_ERR(st->reg);
>>> +
>>> +	ret = regulator_enable(st->reg);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	ret = iio_triggered_buffer_setup(indio_dev, NULL,
>>> +			&tlc4541_trigger_handler, NULL);
>>> +	if (ret)
>>> +		goto error_disable_reg;
>>> +
>>> +	ret = iio_device_register(indio_dev);
>>> +	if (ret)
>>> +		goto error_cleanup_buffer;
>>> +
>>> +	return 0;
>>> +
>>> +error_cleanup_buffer:
>>> +	iio_triggered_buffer_cleanup(indio_dev);
>>> +error_disable_reg:
>>> +	regulator_disable(st->reg);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int tlc4541_remove(struct spi_device *spi)
>>> +{
>>> +	struct iio_dev *indio_dev = spi_get_drvdata(spi);
>>> +	struct tlc4541_state *st = iio_priv(indio_dev);
>>> +
>>> +	iio_device_unregister(indio_dev);
>>> +	iio_triggered_buffer_cleanup(indio_dev);
>>> +	regulator_disable(st->reg);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +#ifdef CONFIG_OF
>
> maybe drop the newlines here?
ok
>
>>> +
>>> +static const struct of_device_id tlc4541_dt_ids[] = {
>>> +	{ .compatible = "ti,tlc3541", },
>>> +	{ .compatible = "ti,tlc4541", },
>>> +	{}
>>> +};
>>> +MODULE_DEVICE_TABLE(of, tlc4541_dt_ids);
>>> +
>>> +#endif
>>> +
>>> +static const struct spi_device_id tlc4541_id[] = {
>>> +	{"tlc3541", TLC3541},
>>> +	{"tlc4541", TLC4541},
>>> +	{}
>>> +};
>>> +MODULE_DEVICE_TABLE(spi, tlc4541_id);
>>> +
>>> +static struct spi_driver tlc4541_driver = {
>>> +	.driver = {
>>> +		.name   = "tlc4541",
>>> +		.of_match_table = of_match_ptr(tlc4541_dt_ids),
>>> +	},
>>> +	.probe          = tlc4541_probe,
>>> +	.remove         = tlc4541_remove,
>>> +	.id_table       = tlc4541_id,
>>> +};
>>> +module_spi_driver(tlc4541_driver);
>>> +
>>> +MODULE_AUTHOR("Phil Reid <preid-qgqNFa1JUf/o2iN0hyhwsIdd74u8MsAO@public.gmane.org>");
>>> +MODULE_DESCRIPTION("Texas Instruments TLC4541 ADC");
>>> +MODULE_LICENSE("GPL v2");
>>>
>>
>>
>>
>


-- 
Regards
Phil Reid

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* [PATCHv4 3/8] rtc: add STM32 RTC driver
From: Amelie Delaunay @ 2017-01-11 12:48 UTC (permalink / raw)
  To: Alessandro Zummo, Alexandre Belloni, Rob Herring, Mark Rutland,
	Maxime Coquelin, Alexandre Torgue, Russell King
  Cc: rtc-linux-/JYPxA39Uh5TLH3MbocFFw,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Gabriel Fernandez,
	Amelie Delaunay

This patch adds support for the STM32 RTC.

Signed-off-by: Amelie Delaunay <amelie.delaunay-qxv4g6HH51o@public.gmane.org>
---
 drivers/rtc/Kconfig     |  11 +
 drivers/rtc/Makefile    |   1 +
 drivers/rtc/rtc-stm32.c | 727 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 739 insertions(+)
 create mode 100644 drivers/rtc/rtc-stm32.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index e859d14..11eb28a 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1706,6 +1706,17 @@ config RTC_DRV_PIC32
 	   This driver can also be built as a module. If so, the module
 	   will be called rtc-pic32
 
+config RTC_DRV_STM32
+	tristate "STM32 RTC"
+	select REGMAP_MMIO
+	depends on ARCH_STM32 || COMPILE_TEST
+	help
+	   If you say yes here you get support for the STM32 On-Chip
+	   Real Time Clock.
+
+	   This driver can also be built as a module, if so, the module
+	   will be called "rtc-stm32".
+
 comment "HID Sensor RTC drivers"
 
 config RTC_DRV_HID_SENSOR_TIME
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 1ac694a..87bd9cc 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -144,6 +144,7 @@ obj-$(CONFIG_RTC_DRV_SNVS)	+= rtc-snvs.o
 obj-$(CONFIG_RTC_DRV_SPEAR)	+= rtc-spear.o
 obj-$(CONFIG_RTC_DRV_STARFIRE)	+= rtc-starfire.o
 obj-$(CONFIG_RTC_DRV_STK17TA8)	+= rtc-stk17ta8.o
+obj-$(CONFIG_RTC_DRV_STM32) 	+= rtc-stm32.o
 obj-$(CONFIG_RTC_DRV_STMP)	+= rtc-stmp3xxx.o
 obj-$(CONFIG_RTC_DRV_ST_LPC)	+= rtc-st-lpc.o
 obj-$(CONFIG_RTC_DRV_SUN4V)	+= rtc-sun4v.o
diff --git a/drivers/rtc/rtc-stm32.c b/drivers/rtc/rtc-stm32.c
new file mode 100644
index 0000000..f49226e
--- /dev/null
+++ b/drivers/rtc/rtc-stm32.c
@@ -0,0 +1,727 @@
+/*
+ * Copyright (C) Amelie Delaunay 2016
+ * Author:  Amelie Delaunay <amelie.delaunay-qxv4g6HH51o@public.gmane.org>
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#include <linux/bcd.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/ioport.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+
+#define DRIVER_NAME "stm32_rtc"
+
+/* STM32 RTC registers */
+#define STM32_RTC_TR		0x00
+#define STM32_RTC_DR		0x04
+#define STM32_RTC_CR		0x08
+#define STM32_RTC_ISR		0x0C
+#define STM32_RTC_PRER		0x10
+#define STM32_RTC_ALRMAR	0x1C
+#define STM32_RTC_WPR		0x24
+
+/* STM32_RTC_TR bit fields  */
+#define STM32_RTC_TR_SEC_SHIFT		0
+#define STM32_RTC_TR_SEC		GENMASK(6, 0)
+#define STM32_RTC_TR_MIN_SHIFT		8
+#define STM32_RTC_TR_MIN		GENMASK(14, 8)
+#define STM32_RTC_TR_HOUR_SHIFT		16
+#define STM32_RTC_TR_HOUR		GENMASK(21, 16)
+
+/* STM32_RTC_DR bit fields */
+#define STM32_RTC_DR_DATE_SHIFT		0
+#define STM32_RTC_DR_DATE		GENMASK(5, 0)
+#define STM32_RTC_DR_MONTH_SHIFT	8
+#define STM32_RTC_DR_MONTH		GENMASK(12, 8)
+#define STM32_RTC_DR_WDAY_SHIFT		13
+#define STM32_RTC_DR_WDAY		GENMASK(15, 13)
+#define STM32_RTC_DR_YEAR_SHIFT		16
+#define STM32_RTC_DR_YEAR		GENMASK(23, 16)
+
+/* STM32_RTC_CR bit fields */
+#define STM32_RTC_CR_FMT		BIT(6)
+#define STM32_RTC_CR_ALRAE		BIT(8)
+#define STM32_RTC_CR_ALRAIE		BIT(12)
+
+/* STM32_RTC_ISR bit fields */
+#define STM32_RTC_ISR_ALRAWF		BIT(0)
+#define STM32_RTC_ISR_INITS		BIT(4)
+#define STM32_RTC_ISR_RSF		BIT(5)
+#define STM32_RTC_ISR_INITF		BIT(6)
+#define STM32_RTC_ISR_INIT		BIT(7)
+#define STM32_RTC_ISR_ALRAF		BIT(8)
+
+/* STM32_RTC_PRER bit fields */
+#define STM32_RTC_PRER_PRED_S_SHIFT	0
+#define STM32_RTC_PRER_PRED_S		GENMASK(14, 0)
+#define STM32_RTC_PRER_PRED_A_SHIFT	16
+#define STM32_RTC_PRER_PRED_A		GENMASK(22, 16)
+
+/* STM32_RTC_ALRMAR and STM32_RTC_ALRMBR bit fields */
+#define STM32_RTC_ALRMXR_SEC_SHIFT	0
+#define STM32_RTC_ALRMXR_SEC		GENMASK(6, 0)
+#define STM32_RTC_ALRMXR_SEC_MASK	BIT(7)
+#define STM32_RTC_ALRMXR_MIN_SHIFT	8
+#define STM32_RTC_ALRMXR_MIN		GENMASK(14, 8)
+#define STM32_RTC_ALRMXR_MIN_MASK	BIT(15)
+#define STM32_RTC_ALRMXR_HOUR_SHIFT	16
+#define STM32_RTC_ALRMXR_HOUR		GENMASK(21, 16)
+#define STM32_RTC_ALRMXR_PM		BIT(22)
+#define STM32_RTC_ALRMXR_HOUR_MASK	BIT(23)
+#define STM32_RTC_ALRMXR_DATE_SHIFT	24
+#define STM32_RTC_ALRMXR_DATE		GENMASK(29, 24)
+#define STM32_RTC_ALRMXR_WDSEL		BIT(30)
+#define STM32_RTC_ALRMXR_WDAY_SHIFT	24
+#define STM32_RTC_ALRMXR_WDAY		GENMASK(27, 24)
+#define STM32_RTC_ALRMXR_DATE_MASK	BIT(31)
+
+/* STM32_RTC_WPR key constants */
+#define RTC_WPR_1ST_KEY			0xCA
+#define RTC_WPR_2ND_KEY			0x53
+#define RTC_WPR_WRONG_KEY		0xFF
+
+/*
+ * RTC registers are protected agains parasitic write access.
+ * PWR_CR_DBP bit must be set to enable write access to RTC registers.
+ */
+/* STM32_PWR_CR */
+#define PWR_CR				0x00
+/* STM32_PWR_CR bit field */
+#define PWR_CR_DBP			BIT(8)
+
+static struct regmap *dbp;
+
+struct stm32_rtc {
+	struct rtc_device *rtc_dev;
+	void __iomem *base;
+	struct clk *ck_rtc;
+	int irq_alarm;
+};
+
+static void stm32_rtc_wpr_unlock(struct stm32_rtc *rtc)
+{
+	writel_relaxed(RTC_WPR_1ST_KEY, rtc->base + STM32_RTC_WPR);
+	writel_relaxed(RTC_WPR_2ND_KEY, rtc->base + STM32_RTC_WPR);
+}
+
+static void stm32_rtc_wpr_lock(struct stm32_rtc *rtc)
+{
+	writel_relaxed(RTC_WPR_WRONG_KEY, rtc->base + STM32_RTC_WPR);
+}
+
+static int stm32_rtc_enter_init_mode(struct stm32_rtc *rtc)
+{
+	unsigned int isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+
+	if (!(isr & STM32_RTC_ISR_INITF)) {
+		isr |= STM32_RTC_ISR_INIT;
+		writel_relaxed(isr, rtc->base + STM32_RTC_ISR);
+
+		/*
+		 * It takes around 2 ck_rtc clock cycles to enter in
+		 * initialization phase mode (and have INITF flag set). As
+		 * slowest ck_rtc frequency may be 32kHz and highest should be
+		 * 1MHz, we poll every 10 us with a timeout of 100ms.
+		 */
+		return readl_relaxed_poll_timeout_atomic(
+					rtc->base + STM32_RTC_ISR,
+					isr, (isr & STM32_RTC_ISR_INITF),
+					10, 100000);
+	}
+
+	return 0;
+}
+
+static void stm32_rtc_exit_init_mode(struct stm32_rtc *rtc)
+{
+	unsigned int isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+
+	isr &= ~STM32_RTC_ISR_INIT;
+	writel_relaxed(isr, rtc->base + STM32_RTC_ISR);
+}
+
+static int stm32_rtc_wait_sync(struct stm32_rtc *rtc)
+{
+	unsigned int isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+
+	isr &= ~STM32_RTC_ISR_RSF;
+	writel_relaxed(isr, rtc->base + STM32_RTC_ISR);
+
+	/*
+	 * Wait for RSF to be set to ensure the calendar registers are
+	 * synchronised, it takes around 2 ck_rtc clock cycles
+	 */
+	return readl_relaxed_poll_timeout_atomic(rtc->base + STM32_RTC_ISR,
+						 isr,
+						 (isr & STM32_RTC_ISR_RSF),
+						 10, 100000);
+}
+
+static irqreturn_t stm32_rtc_alarm_irq(int irq, void *dev_id)
+{
+	struct stm32_rtc *rtc = (struct stm32_rtc *)dev_id;
+	unsigned int isr, cr;
+
+	mutex_lock(&rtc->rtc_dev->ops_lock);
+
+	isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+
+	if ((isr & STM32_RTC_ISR_ALRAF) &&
+	    (cr & STM32_RTC_CR_ALRAIE)) {
+		/* Alarm A flag - Alarm interrupt */
+		dev_dbg(&rtc->rtc_dev->dev, "Alarm occurred\n");
+
+		/* Pass event to the kernel */
+		rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+		/* Clear event flag, otherwise new events won't be received */
+		writel_relaxed(isr & ~STM32_RTC_ISR_ALRAF,
+			       rtc->base + STM32_RTC_ISR);
+	}
+
+	mutex_unlock(&rtc->rtc_dev->ops_lock);
+
+	return IRQ_HANDLED;
+}
+
+/* Convert rtc_time structure from bin to bcd format */
+static void tm2bcd(struct rtc_time *tm)
+{
+	tm->tm_sec = bin2bcd(tm->tm_sec);
+	tm->tm_min = bin2bcd(tm->tm_min);
+	tm->tm_hour = bin2bcd(tm->tm_hour);
+
+	tm->tm_mday = bin2bcd(tm->tm_mday);
+	tm->tm_mon = bin2bcd(tm->tm_mon + 1);
+	tm->tm_year = bin2bcd(tm->tm_year - 100);
+	/*
+	 * Number of days since Sunday
+	 * - on kernel side, 0=Sunday...6=Saturday
+	 * - on rtc side, 0=invalid,1=Monday...7=Sunday
+	 */
+	tm->tm_wday = (!tm->tm_wday) ? 7 : tm->tm_wday;
+}
+
+/* Convert rtc_time structure from bcd to bin format */
+static void bcd2tm(struct rtc_time *tm)
+{
+	tm->tm_sec = bcd2bin(tm->tm_sec);
+	tm->tm_min = bcd2bin(tm->tm_min);
+	tm->tm_hour = bcd2bin(tm->tm_hour);
+
+	tm->tm_mday = bcd2bin(tm->tm_mday);
+	tm->tm_mon = bcd2bin(tm->tm_mon) - 1;
+	tm->tm_year = bcd2bin(tm->tm_year) + 100;
+	/*
+	 * Number of days since Sunday
+	 * - on kernel side, 0=Sunday...6=Saturday
+	 * - on rtc side, 0=invalid,1=Monday...7=Sunday
+	 */
+	tm->tm_wday %= 7;
+}
+
+static int stm32_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	unsigned int tr, dr;
+
+	/* Time and Date in BCD format */
+	tr = readl_relaxed(rtc->base + STM32_RTC_TR);
+	dr = readl_relaxed(rtc->base + STM32_RTC_DR);
+
+	tm->tm_sec = (tr & STM32_RTC_TR_SEC) >> STM32_RTC_TR_SEC_SHIFT;
+	tm->tm_min = (tr & STM32_RTC_TR_MIN) >> STM32_RTC_TR_MIN_SHIFT;
+	tm->tm_hour = (tr & STM32_RTC_TR_HOUR) >> STM32_RTC_TR_HOUR_SHIFT;
+
+	tm->tm_mday = (dr & STM32_RTC_DR_DATE) >> STM32_RTC_DR_DATE_SHIFT;
+	tm->tm_mon = (dr & STM32_RTC_DR_MONTH) >> STM32_RTC_DR_MONTH_SHIFT;
+	tm->tm_year = (dr & STM32_RTC_DR_YEAR) >> STM32_RTC_DR_YEAR_SHIFT;
+	tm->tm_wday = (dr & STM32_RTC_DR_WDAY) >> STM32_RTC_DR_WDAY_SHIFT;
+
+	/* We don't report tm_yday and tm_isdst */
+
+	bcd2tm(tm);
+
+	return 0;
+}
+
+static int stm32_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	unsigned int tr, dr;
+	int ret = 0;
+
+	tm2bcd(tm);
+
+	/* Time in BCD format */
+	tr = ((tm->tm_sec << STM32_RTC_TR_SEC_SHIFT) & STM32_RTC_TR_SEC) |
+	     ((tm->tm_min << STM32_RTC_TR_MIN_SHIFT) & STM32_RTC_TR_MIN) |
+	     ((tm->tm_hour << STM32_RTC_TR_HOUR_SHIFT) & STM32_RTC_TR_HOUR);
+
+	/* Date in BCD format */
+	dr = ((tm->tm_mday << STM32_RTC_DR_DATE_SHIFT) & STM32_RTC_DR_DATE) |
+	     ((tm->tm_mon << STM32_RTC_DR_MONTH_SHIFT) & STM32_RTC_DR_MONTH) |
+	     ((tm->tm_year << STM32_RTC_DR_YEAR_SHIFT) & STM32_RTC_DR_YEAR) |
+	     ((tm->tm_wday << STM32_RTC_DR_WDAY_SHIFT) & STM32_RTC_DR_WDAY);
+
+	stm32_rtc_wpr_unlock(rtc);
+
+	ret = stm32_rtc_enter_init_mode(rtc);
+	if (ret) {
+		dev_err(dev, "Can't enter in init mode. Set time aborted.\n");
+		goto end;
+	}
+
+	writel_relaxed(tr, rtc->base + STM32_RTC_TR);
+	writel_relaxed(dr, rtc->base + STM32_RTC_DR);
+
+	stm32_rtc_exit_init_mode(rtc);
+
+	ret = stm32_rtc_wait_sync(rtc);
+end:
+	stm32_rtc_wpr_lock(rtc);
+
+	return ret;
+}
+
+static int stm32_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	struct rtc_time *tm = &alrm->time;
+	unsigned int alrmar, cr, isr;
+
+	alrmar = readl_relaxed(rtc->base + STM32_RTC_ALRMAR);
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+	isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+
+	if (alrmar & STM32_RTC_ALRMXR_DATE_MASK) {
+		/*
+		 * Date/day doesn't matter in Alarm comparison so alarm
+		 * triggers every day
+		 */
+		tm->tm_mday = -1;
+		tm->tm_wday = -1;
+	} else {
+		if (alrmar & STM32_RTC_ALRMXR_WDSEL) {
+			/* Alarm is set to a day of week */
+			tm->tm_mday = -1;
+			tm->tm_wday = (alrmar & STM32_RTC_ALRMXR_WDAY) >>
+				      STM32_RTC_ALRMXR_WDAY_SHIFT;
+			tm->tm_wday %= 7;
+		} else {
+			/* Alarm is set to a day of month */
+			tm->tm_wday = -1;
+			tm->tm_mday = (alrmar & STM32_RTC_ALRMXR_DATE) >>
+				       STM32_RTC_ALRMXR_DATE_SHIFT;
+		}
+	}
+
+	if (alrmar & STM32_RTC_ALRMXR_HOUR_MASK) {
+		/* Hours don't matter in Alarm comparison */
+		tm->tm_hour = -1;
+	} else {
+		tm->tm_hour = (alrmar & STM32_RTC_ALRMXR_HOUR) >>
+			       STM32_RTC_ALRMXR_HOUR_SHIFT;
+		if (alrmar & STM32_RTC_ALRMXR_PM)
+			tm->tm_hour += 12;
+	}
+
+	if (alrmar & STM32_RTC_ALRMXR_MIN_MASK) {
+		/* Minutes don't matter in Alarm comparison */
+		tm->tm_min = -1;
+	} else {
+		tm->tm_min = (alrmar & STM32_RTC_ALRMXR_MIN) >>
+			      STM32_RTC_ALRMXR_MIN_SHIFT;
+	}
+
+	if (alrmar & STM32_RTC_ALRMXR_SEC_MASK) {
+		/* Seconds don't matter in Alarm comparison */
+		tm->tm_sec = -1;
+	} else {
+		tm->tm_sec = (alrmar & STM32_RTC_ALRMXR_SEC) >>
+			      STM32_RTC_ALRMXR_SEC_SHIFT;
+	}
+
+	bcd2tm(tm);
+
+	alrm->enabled = (cr & STM32_RTC_CR_ALRAE) ? 1 : 0;
+	alrm->pending = (isr & STM32_RTC_ISR_ALRAF) ? 1 : 0;
+
+	return 0;
+}
+
+static int stm32_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	unsigned int isr, cr;
+
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+
+	stm32_rtc_wpr_unlock(rtc);
+
+	/* We expose Alarm A to the kernel */
+	if (enabled)
+		cr |= (STM32_RTC_CR_ALRAIE | STM32_RTC_CR_ALRAE);
+	else
+		cr &= ~(STM32_RTC_CR_ALRAIE | STM32_RTC_CR_ALRAE);
+	writel_relaxed(cr, rtc->base + STM32_RTC_CR);
+
+	/* Clear event flag, otherwise new events won't be received */
+	isr = readl_relaxed(rtc->base + STM32_RTC_ISR);
+	isr &= ~STM32_RTC_ISR_ALRAF;
+	writel_relaxed(isr, rtc->base + STM32_RTC_ISR);
+
+	stm32_rtc_wpr_lock(rtc);
+
+	return 0;
+}
+
+static int stm32_rtc_valid_alrm(struct stm32_rtc *rtc, struct rtc_time *tm)
+{
+	unsigned int cur_day, cur_mon, cur_year, cur_hour, cur_min, cur_sec;
+	unsigned int dr = readl_relaxed(rtc->base + STM32_RTC_DR);
+	unsigned int tr = readl_relaxed(rtc->base + STM32_RTC_TR);
+
+	cur_day = (dr & STM32_RTC_DR_DATE) >> STM32_RTC_DR_DATE_SHIFT;
+	cur_mon = (dr & STM32_RTC_DR_MONTH) >> STM32_RTC_DR_MONTH_SHIFT;
+	cur_year = (dr & STM32_RTC_DR_YEAR) >> STM32_RTC_DR_YEAR_SHIFT;
+	cur_sec = (tr & STM32_RTC_TR_SEC) >> STM32_RTC_TR_SEC_SHIFT;
+	cur_min = (tr & STM32_RTC_TR_MIN) >> STM32_RTC_TR_MIN_SHIFT;
+	cur_hour = (tr & STM32_RTC_TR_HOUR) >> STM32_RTC_TR_HOUR_SHIFT;
+
+	/*
+	 * Assuming current date is M-D-Y H:M:S.
+	 * RTC alarm can't be set on a specific month and year.
+	 * So the valid alarm range is:
+	 *	M-D-Y H:M:S < alarm <= (M+1)-D-Y H:M:S
+	 * with a specific case for December...
+	 */
+	if ((((tm->tm_year > cur_year) &&
+	      (tm->tm_mon == 0x1) && (cur_mon == 0x12)) ||
+	     ((tm->tm_year == cur_year) &&
+	      (tm->tm_mon <= cur_mon + 1))) &&
+	    ((tm->tm_mday > cur_day) ||
+	     ((tm->tm_mday == cur_day) &&
+	     ((tm->tm_hour > cur_hour) ||
+	      ((tm->tm_hour == cur_hour) && (tm->tm_min > cur_min)) ||
+	      ((tm->tm_hour == cur_hour) && (tm->tm_min == cur_min) &&
+	       (tm->tm_sec >= cur_sec))))))
+		return 0;
+
+	return -EINVAL;
+}
+
+static int stm32_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	struct rtc_time *tm = &alrm->time;
+	unsigned int cr, isr, alrmar;
+	int ret = 0;
+
+	tm2bcd(tm);
+
+	/*
+	 * RTC alarm can't be set on a specific date, unless this date is
+	 * up to the same day of month next month.
+	 */
+	if (stm32_rtc_valid_alrm(rtc, tm) < 0) {
+		dev_err(dev, "Alarm can be set only on upcoming month.\n");
+		return -EINVAL;
+	}
+
+	alrmar = 0;
+	/* tm_year and tm_mon are not used because not supported by RTC */
+	alrmar |= (tm->tm_mday << STM32_RTC_ALRMXR_DATE_SHIFT) &
+		  STM32_RTC_ALRMXR_DATE;
+	/* 24-hour format */
+	alrmar &= ~STM32_RTC_ALRMXR_PM;
+	alrmar |= (tm->tm_hour << STM32_RTC_ALRMXR_HOUR_SHIFT) &
+		  STM32_RTC_ALRMXR_HOUR;
+	alrmar |= (tm->tm_min << STM32_RTC_ALRMXR_MIN_SHIFT) &
+		  STM32_RTC_ALRMXR_MIN;
+	alrmar |= (tm->tm_sec << STM32_RTC_ALRMXR_SEC_SHIFT) &
+		  STM32_RTC_ALRMXR_SEC;
+
+	stm32_rtc_wpr_unlock(rtc);
+
+	/* Disable Alarm */
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+	cr &= ~STM32_RTC_CR_ALRAE;
+	writel_relaxed(cr, rtc->base + STM32_RTC_CR);
+
+	/*
+	 * Poll Alarm write flag to be sure that Alarm update is allowed: it
+	 * takes around 2 ck_rtc clock cycles
+	 */
+	ret = readl_relaxed_poll_timeout_atomic(rtc->base + STM32_RTC_ISR,
+						isr,
+						(isr & STM32_RTC_ISR_ALRAWF),
+						10, 100000);
+
+	if (ret) {
+		dev_err(dev, "Alarm update not allowed\n");
+		goto end;
+	}
+
+	/* Write to Alarm register */
+	writel_relaxed(alrmar, rtc->base + STM32_RTC_ALRMAR);
+
+	if (alrm->enabled)
+		stm32_rtc_alarm_irq_enable(dev, 1);
+	else
+		stm32_rtc_alarm_irq_enable(dev, 0);
+
+end:
+	stm32_rtc_wpr_lock(rtc);
+
+	return ret;
+}
+
+static const struct rtc_class_ops stm32_rtc_ops = {
+	.read_time	= stm32_rtc_read_time,
+	.set_time	= stm32_rtc_set_time,
+	.read_alarm	= stm32_rtc_read_alarm,
+	.set_alarm	= stm32_rtc_set_alarm,
+	.alarm_irq_enable = stm32_rtc_alarm_irq_enable,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id stm32_rtc_of_match[] = {
+	{ .compatible = "st,stm32-rtc" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, stm32_rtc_of_match);
+#endif
+
+static int stm32_rtc_init(struct platform_device *pdev,
+			  struct stm32_rtc *rtc)
+{
+	unsigned int prer, pred_a, pred_s, pred_a_max, pred_s_max, cr;
+	unsigned int rate;
+	int ret = 0;
+
+	rate = clk_get_rate(rtc->ck_rtc);
+
+	/* Find prediv_a and prediv_s to obtain the 1Hz calendar clock */
+	pred_a_max = STM32_RTC_PRER_PRED_A >> STM32_RTC_PRER_PRED_A_SHIFT;
+	pred_s_max = STM32_RTC_PRER_PRED_S >> STM32_RTC_PRER_PRED_S_SHIFT;
+
+	for (pred_a = pred_a_max; pred_a >= 0; pred_a--) {
+		pred_s = (rate / (pred_a + 1)) - 1;
+
+		if (((pred_s + 1) * (pred_a + 1)) == rate)
+			break;
+	}
+
+	/*
+	 * Can't find a 1Hz, so give priority to RTC power consumption
+	 * by choosing the higher possible value for prediv_a
+	 */
+	if ((pred_s > pred_s_max) || (pred_a > pred_a_max)) {
+		pred_a = pred_a_max;
+		pred_s = (rate / (pred_a + 1)) - 1;
+
+		dev_warn(&pdev->dev, "ck_rtc is %s\n",
+			 (rate - ((pred_a + 1) * (pred_s + 1)) < 0) ?
+			 "fast" : "slow");
+	}
+
+	stm32_rtc_wpr_unlock(rtc);
+
+	ret = stm32_rtc_enter_init_mode(rtc);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"Can't enter in init mode. Prescaler config failed.\n");
+		goto end;
+	}
+
+	prer = (pred_s << STM32_RTC_PRER_PRED_S_SHIFT) & STM32_RTC_PRER_PRED_S;
+	writel_relaxed(prer, rtc->base + STM32_RTC_PRER);
+	prer |= (pred_a << STM32_RTC_PRER_PRED_A_SHIFT) & STM32_RTC_PRER_PRED_A;
+	writel_relaxed(prer, rtc->base + STM32_RTC_PRER);
+
+	/* Force 24h time format */
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+	cr &= ~STM32_RTC_CR_FMT;
+	writel_relaxed(cr, rtc->base + STM32_RTC_CR);
+
+	stm32_rtc_exit_init_mode(rtc);
+
+	ret = stm32_rtc_wait_sync(rtc);
+end:
+	stm32_rtc_wpr_lock(rtc);
+
+	return ret;
+}
+
+static int stm32_rtc_probe(struct platform_device *pdev)
+{
+	struct stm32_rtc *rtc;
+	struct resource *res;
+	int ret;
+
+	rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
+	if (!rtc)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	rtc->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(rtc->base))
+		return PTR_ERR(rtc->base);
+
+	dbp = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "st,syscfg");
+	if (IS_ERR(dbp)) {
+		dev_err(&pdev->dev, "no st,syscfg\n");
+		return PTR_ERR(dbp);
+	}
+
+	rtc->ck_rtc = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(rtc->ck_rtc)) {
+		dev_err(&pdev->dev, "no ck_rtc clock");
+		return PTR_ERR(rtc->ck_rtc);
+	}
+
+	ret = clk_prepare_enable(rtc->ck_rtc);
+	if (ret)
+		return ret;
+
+	regmap_update_bits(dbp, PWR_CR, PWR_CR_DBP, PWR_CR_DBP);
+
+	/*
+	 * After a system reset, RTC_ISR.INITS flag can be read to check if
+	 * the calendar has been initalized or not. INITS flag is reset by a
+	 * power-on reset (no vbat, no power-supply). It is not reset if
+	 * ck_rtc parent clock has changed (so RTC prescalers need to be
+	 * changed). That's why we cannot rely on this flag to know if RTC
+	 * init has to be done.
+	 */
+	ret = stm32_rtc_init(pdev, rtc);
+	if (ret)
+		goto err;
+
+	rtc->irq_alarm = platform_get_irq(pdev, 0);
+	if (rtc->irq_alarm <= 0) {
+		dev_err(&pdev->dev, "no alarm irq\n");
+		ret = rtc->irq_alarm;
+		goto err;
+	}
+
+	platform_set_drvdata(pdev, rtc);
+
+	ret = device_init_wakeup(&pdev->dev, true);
+	if (ret)
+		dev_warn(&pdev->dev,
+			 "alarm won't be able to wake up the system");
+
+	rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name,
+			&stm32_rtc_ops, THIS_MODULE);
+	if (IS_ERR(rtc->rtc_dev)) {
+		ret = PTR_ERR(rtc->rtc_dev);
+		dev_err(&pdev->dev, "rtc device registration failed, err=%d\n",
+			ret);
+		goto err;
+	}
+
+	/* Handle RTC alarm interrupts */
+	ret = devm_request_threaded_irq(&pdev->dev, rtc->irq_alarm, NULL,
+					stm32_rtc_alarm_irq,
+					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+					pdev->name, rtc);
+	if (ret) {
+		dev_err(&pdev->dev, "IRQ%d (alarm interrupt) already claimed\n",
+			rtc->irq_alarm);
+		goto err;
+	}
+
+	/*
+	 * If INITS flag is reset (calendar year field set to 0x00), calendar
+	 * must be initialized
+	 */
+	if (!(readl_relaxed(rtc->base + STM32_RTC_ISR) & STM32_RTC_ISR_INITS))
+		dev_warn(&pdev->dev, "Date/Time must be initialized\n");
+
+	return 0;
+err:
+	clk_disable_unprepare(rtc->ck_rtc);
+
+	regmap_update_bits(dbp, PWR_CR, PWR_CR_DBP, ~PWR_CR_DBP);
+
+	device_init_wakeup(&pdev->dev, false);
+
+	return ret;
+}
+
+static int __exit stm32_rtc_remove(struct platform_device *pdev)
+{
+	struct stm32_rtc *rtc = platform_get_drvdata(pdev);
+	unsigned int cr;
+
+	/* Disable interrupts */
+	stm32_rtc_wpr_unlock(rtc);
+	cr = readl_relaxed(rtc->base + STM32_RTC_CR);
+	cr &= ~STM32_RTC_CR_ALRAIE;
+	writel_relaxed(cr, rtc->base + STM32_RTC_CR);
+	stm32_rtc_wpr_lock(rtc);
+
+	clk_disable_unprepare(rtc->ck_rtc);
+
+	/* Enable backup domain write protection */
+	regmap_update_bits(dbp, PWR_CR, PWR_CR_DBP, ~PWR_CR_DBP);
+
+	device_init_wakeup(&pdev->dev, false);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int stm32_rtc_suspend(struct device *dev)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+
+	if (device_may_wakeup(dev))
+		return enable_irq_wake(rtc->irq_alarm);
+
+	return 0;
+}
+
+static int stm32_rtc_resume(struct device *dev)
+{
+	struct stm32_rtc *rtc = dev_get_drvdata(dev);
+	int ret = 0;
+
+	ret = stm32_rtc_wait_sync(rtc);
+	if (ret < 0)
+		return ret;
+
+	if (device_may_wakeup(dev))
+		return disable_irq_wake(rtc->irq_alarm);
+
+	return ret;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(stm32_rtc_pm_ops,
+			 stm32_rtc_suspend, stm32_rtc_resume);
+
+static struct platform_driver stm32_rtc_driver = {
+	.probe		= stm32_rtc_probe,
+	.remove		= stm32_rtc_remove,
+	.driver		= {
+		.name	= DRIVER_NAME,
+		.pm	= &stm32_rtc_pm_ops,
+		.of_match_table = stm32_rtc_of_match,
+	},
+};
+
+module_platform_driver(stm32_rtc_driver);
+
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay-qxv4g6HH51o@public.gmane.org>");
+MODULE_DESCRIPTION("STMicroelectronics STM32 Real Time Clock driver");
+MODULE_LICENSE("GPL v2");
-- 
1.9.1

-- 
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
--- 
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply related

* Re: [PATCH v8 1/3] dt-bindings: Add support for samsung s6e3ha2 panel binding
From: Chanwoo Choi @ 2017-01-11 12:02 UTC (permalink / raw)
  To: Hoegeun Kwon
  Cc: Krzysztof Kozlowski, Rob Herring, thierry.reding, airlied,
	Kukjin Kim, devicetree, linux-samsung-soc, Chanwoo Choi,
	Donghwa Lee, linux-kernel, dri-devel, jh80.chung@samsung.com,
	Andi Shyti, Hyungwon Hwang
In-Reply-To: <1484116439-7275-2-git-send-email-hoegeun.kwon@samsung.com>

Hi Hoegeun,

2017-01-11 15:33 GMT+09:00 Hoegeun Kwon <hoegeun.kwon@samsung.com>:
> The Samsung s6e3ha2 is a 5.7" 1440x2560 AMOLED panel connected
> using MIPI-DSI interfaces.
>
> Signed-off-by: Donghwa Lee <dh09.lee@samsung.com>
> Signed-off-by: Hyungwon Hwang <human.hwang@samsung.com>
> Signed-off-by: Hoegeun Kwon <hoegeun.kwon@samsung.com>
> Tested-by: Chanwoo Choi <cw00.choi@samsung.com>

I think my tested-by tag is improper against binding documentation.
Maybe you added the my tested-by tag when you split the panel
driver because I replied my tested-by tag to panel driver.

You better to drop the my tested-by tag from only this patch
(binding documentation).

> Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>
> ---
>  .../bindings/display/panel/samsung,s6e3ha2.txt     | 26 ++++++++++++++++++++++
>  1 file changed, 26 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/panel/samsung,s6e3ha2.txt
>
> diff --git a/Documentation/devicetree/bindings/display/panel/samsung,s6e3ha2.txt b/Documentation/devicetree/bindings/display/panel/samsung,s6e3ha2.txt
> new file mode 100644
> index 0000000..3e7892c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/panel/samsung,s6e3ha2.txt
> @@ -0,0 +1,26 @@
> +Samsung S6E3HA2 5.7" 1440x2560 AMOLED panel
> +
> +Required properties:
> +  - compatible: "samsung,s6e3ha2"
> +  - reg: the virtual channel number of a DSI peripheral
> +  - vdd3-supply: I/O voltage supply
> +  - vci-supply: voltage supply for analog circuits
> +  - reset-gpios: a GPIO spec for the reset pin (active low)
> +  - enable-gpios: a GPIO spec for the panel enable pin (active high)
> +  - te-gpios: a GPIO spec for the tearing effect synchronization signal
> +    gpio pin (active high)
> +
> +Example:
> +&dsi {
> +       ...
> +
> +       panel@0 {
> +               compatible = "samsung,s6e3ha2";
> +               reg = <0>;
> +               vdd3-supply = <&ldo27_reg>;
> +               vci-supply = <&ldo28_reg>;
> +               reset-gpios = <&gpg0 0 GPIO_ACTIVE_LOW>;
> +               enable-gpios = <&gpf1 5 GPIO_ACTIVE_HIGH>;
> +               te-gpios = <&gpf1 3 GPIO_ACTIVE_HIGH>;
> +       };
> +};
> --
> 1.9.1
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel



-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

^ permalink raw reply

* Re: [PATCH v2 2/2] Support for DW CSI-2 Host IPK
From: Hans Verkuil @ 2017-01-11 11:54 UTC (permalink / raw)
  To: Ramiro Oliveira, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	mark.rutland-5wv7dgnIgG8, mchehab-DgEjT+Ai2ygdnm+yROfE0A,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-media-u79uwXL29TY76Z2rM5mHXA
  Cc: davem-fT/PcQaiUtIeIZ0/mPfg9Q,
	gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r,
	geert+renesas-gXvu3+zWzMSzQB+pC5nmwQ,
	akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b,
	linux-0h96xk9xTtrk1uMJSBkQmQ,
	laurent.pinchart+renesas-ryLnwIuWjnjg/C1BVhZhaw, arnd-r2nGTMty4D4,
	sudipm.mukherjee-Re5JQEeQqe8AvxtiuMwx3w,
	tiffany.lin-NuS5LvNUpcJWk0Htik3J/w,
	minghsiu.tsai-NuS5LvNUpcJWk0Htik3J/w,
	jean-christophe.trotin-qxv4g6HH51o,
	andrew-ct.chen-NuS5LvNUpcJWk0Htik3J/w,
	simon.horman-wFxRvT7yatFl57MIdRCFDg,
	songjun.wu-UWL1GkI3JZL3oGB3hsPCZA, bparrot-l0cyMroinI0,
	CARLOS.PALMINHA-HKixBCOQz3hWk0Htik3J/w, Sakari Ailus
In-Reply-To: <bf2f0a6730e4a74d64e04575859d6b195f65b368.1481554324.git.roliveir-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>

Hi Ramiro,

See my review comments below:

On 12/12/16 16:00, Ramiro Oliveira wrote:
> Add support for the DesignWare CSI-2 Host IP Prototyping Kit
>
> Signed-off-by: Ramiro Oliveira <roliveir-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
> ---
>  MAINTAINERS                                 |   7 +
>  drivers/media/platform/Kconfig              |   1 +
>  drivers/media/platform/Makefile             |   2 +
>  drivers/media/platform/dwc/Kconfig          |  36 ++
>  drivers/media/platform/dwc/Makefile         |   3 +
>  drivers/media/platform/dwc/dw_mipi_csi.c    | 647 ++++++++++++++++++++++
>  drivers/media/platform/dwc/dw_mipi_csi.h    | 180 ++++++
>  drivers/media/platform/dwc/plat_ipk.c       | 818 ++++++++++++++++++++++++++++
>  drivers/media/platform/dwc/plat_ipk.h       | 101 ++++
>  drivers/media/platform/dwc/plat_ipk_video.h |  97 ++++
>  drivers/media/platform/dwc/video_device.c   | 707 ++++++++++++++++++++++++
>  drivers/media/platform/dwc/video_device.h   |  85 +++
>  12 files changed, 2684 insertions(+)
>  create mode 100644 drivers/media/platform/dwc/Kconfig
>  create mode 100644 drivers/media/platform/dwc/Makefile
>  create mode 100644 drivers/media/platform/dwc/dw_mipi_csi.c
>  create mode 100644 drivers/media/platform/dwc/dw_mipi_csi.h
>  create mode 100644 drivers/media/platform/dwc/plat_ipk.c
>  create mode 100644 drivers/media/platform/dwc/plat_ipk.h
>  create mode 100644 drivers/media/platform/dwc/plat_ipk_video.h
>  create mode 100644 drivers/media/platform/dwc/video_device.c
>  create mode 100644 drivers/media/platform/dwc/video_device.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 72e828a..73250b5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -10651,6 +10651,13 @@ S:	Maintained
>  F:	drivers/staging/media/st-cec/
>  F:	Documentation/devicetree/bindings/media/stih-cec.txt
>
> +SYNOPSYS DESIGNWARE CSI-2 HOST IPK DRIVER
> +M:	Ramiro Oliveira <roliveir-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
> +L:	linux-media-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> +T:	git git://linuxtv.org/media_tree.git
> +S:	Maintained
> +F:	drivers/media/platform/dwc/
> +
>  SYNOPSYS DESIGNWARE DMAC DRIVER
>  M:	Viresh Kumar <vireshk-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
>  M:	Andy Shevchenko <andriy.shevchenko-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index d944421..7b99ba3 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -120,6 +120,7 @@ source "drivers/media/platform/am437x/Kconfig"
>  source "drivers/media/platform/xilinx/Kconfig"
>  source "drivers/media/platform/rcar-vin/Kconfig"
>  source "drivers/media/platform/atmel/Kconfig"
> +source "drivers/media/platform/dwc/Kconfig"
>
>  config VIDEO_TI_CAL
>  	tristate "TI CAL (Camera Adaptation Layer) driver"
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 5b3cb27..d84b1de 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -62,6 +62,8 @@ obj-$(CONFIG_VIDEO_RCAR_VIN)		+= rcar-vin/
>
>  obj-$(CONFIG_VIDEO_ATMEL_ISC)		+= atmel/
>
> +obj-$(CONFIG_VIDEO_DWC)			+= dwc/
> +
>  ccflags-y += -I$(srctree)/drivers/media/i2c
>
>  obj-$(CONFIG_VIDEO_MEDIATEK_VPU)	+= mtk-vpu/
> diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
> new file mode 100644
> index 0000000..fb8533b
> --- /dev/null
> +++ b/drivers/media/platform/dwc/Kconfig
> @@ -0,0 +1,36 @@
> +config VIDEO_DWC
> +	bool "Designware Cores CSI-2 IPK (EXPERIMENTAL)"
> +	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF && HAS_DMA
> +	help
> +	  Say Y here to enable support for the DesignWare Cores CSI-2 Host IP
> +	  Prototyping Kit.
> +
> +if VIDEO_DWC
> +config DWC_PLATFORM
> +	tristate "SNPS DWC MIPI CSI2 Host"
> +	depends on VIDEO_DWC
> +	help
> +	  This is the V4L2 plaftorm driver driver for the DWC CSI-2 HOST IPK

plaftorm -> platform

> +
> +	  To compile this driver as a module, choose M here
> +
> +
> +config DWC_MIPI_CSI2_HOST
> +	tristate "SNPS DWC MIPI CSI2 Host"
> +	select GENERIC_PHY
> +	depends on VIDEO_DWC
> +	help
> +	  This is a V4L2 driver for SNPS DWC MIPI-CSI2
> +
> +	  To compile this driver as a module, choose M here
> +
> +config DWC_VIDEO_DEVICE
> +	tristate "DWC VIDEO DEVICE"
> +	select VIDEOBUF2_VMALLOC
> +	select VIDEOBUF2_DMA_CONTIG
> +	depends on VIDEO_DWC
> +	help
> +	  This is a V4L2 driver for SNPS Video device
> +	  To compile this driver as a module, choose M here
> +
> +endif # VIDEO_DWC
> diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
> new file mode 100644
> index 0000000..75c74b7
> --- /dev/null
> +++ b/drivers/media/platform/dwc/Makefile
> @@ -0,0 +1,3 @@
> +obj-$(CONFIG_DWC_PLATFORM)		+= plat_ipk.o
> +obj-$(CONFIG_DWC_MIPI_CSI2_HOST)	+= dw_mipi_csi.o
> +obj-$(CONFIG_DWC_VIDEO_DEVICE)		+= video_device.o
> diff --git a/drivers/media/platform/dwc/dw_mipi_csi.c b/drivers/media/platform/dwc/dw_mipi_csi.c
> new file mode 100644
> index 0000000..6515afa
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw_mipi_csi.c
> @@ -0,0 +1,647 @@
> +/*
> + * DWC MIPI CSI-2 Host device driver
> + *
> + * Copyright (C) 2016 Synopsys, Inc. All rights reserved.
> + * Author: Ramiro Oliveira <ramiro.oliveira-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published
> + * by the Free Software Foundation, either version 2 of the License,
> + * or (at your option) any later version.
> + */
> +
> +#include "dw_mipi_csi.h"
> +
> +/**
> + * @short Video formats supported by the MIPI CSI-2
> + */
> +static const struct mipi_fmt dw_mipi_csi_formats[] = {
> +	{
> +		/* RAW 8 */
> +		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
> +		.depth = 8,
> +	},
> +	{
> +		/* RAW 10 */
> +		.code = MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE,
> +		.depth = 10,
> +	},
> +	{
> +		/* RGB 565 */
> +		.code = MEDIA_BUS_FMT_RGB565_2X8_BE,
> +		.depth = 16,
> +	},
> +	{
> +		/* BGR 565 */
> +		.code = MEDIA_BUS_FMT_RGB565_2X8_LE,
> +		.depth = 16,
> +	},
> +	{
> +		/* RGB 888 */
> +		.code = MEDIA_BUS_FMT_RGB888_2X12_LE,
> +		.depth = 24,
> +	},
> +	{
> +		/* BGR 888 */
> +		.code = MEDIA_BUS_FMT_RGB888_2X12_BE,
> +		.depth = 24,
> +	},
> +};
> +
> +static struct mipi_csi_dev *sd_to_mipi_csi_dev(struct v4l2_subdev *sdev)
> +{
> +	return container_of(sdev, struct mipi_csi_dev, sd);
> +}
> +
> +static void dw_mipi_csi_write(struct mipi_csi_dev *dev,
> +		  unsigned int address, unsigned int data)
> +{
> +	iowrite32(data, dev->base_address + address);
> +}
> +
> +static u32 dw_mipi_csi_read(struct mipi_csi_dev *dev, unsigned long address)
> +{
> +	return ioread32(dev->base_address + address);
> +}
> +
> +static void dw_mipi_csi_write_part(struct mipi_csi_dev *dev,
> +		       unsigned long address, unsigned long data,
> +		       unsigned char shift, unsigned char width)
> +{
> +	u32 mask = (1 << width) - 1;
> +	u32 temp = dw_mipi_csi_read(dev, address);
> +
> +	temp &= ~(mask << shift);
> +	temp |= (data & mask) << shift;
> +	dw_mipi_csi_write(dev, address, temp);
> +}
> +
> +static const struct mipi_fmt *
> +find_dw_mipi_csi_format(struct v4l2_mbus_framefmt *mf)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(dw_mipi_csi_formats); i++)
> +		if (mf->code == dw_mipi_csi_formats[i].code)
> +			return &dw_mipi_csi_formats[i];
> +	return NULL;
> +}
> +
> +static void dw_mipi_csi_reset(struct mipi_csi_dev *dev)
> +{
> +	dw_mipi_csi_write(dev, R_CSI2_CTRL_RESETN, 0);
> +	dw_mipi_csi_write(dev, R_CSI2_CTRL_RESETN, 1);
> +}
> +
> +static int dw_mipi_csi_mask_irq_power_off(struct mipi_csi_dev *dev)
> +{
> +	/* set only one lane (lane 0) as active (ON) */
> +	dw_mipi_csi_write(dev, R_CSI2_N_LANES, 0);
> +
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_PHY_FATAL, 0);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_PKT_FATAL, 0);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_FRAME_FATAL, 0);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_PHY, 0);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_PKT, 0);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_LINE, 0);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_IPI, 0);
> +
> +	dw_mipi_csi_write(dev, R_CSI2_CTRL_RESETN, 0);
> +
> +	return 0;
> +
> +}
> +
> +static int dw_mipi_csi_hw_stdby(struct mipi_csi_dev *dev)
> +{
> +	/* set only one lane (lane 0) as active (ON) */
> +	dw_mipi_csi_reset(dev);
> +
> +	dw_mipi_csi_write(dev, R_CSI2_N_LANES, 0);
> +
> +	phy_init(dev->phy);
> +
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_PHY_FATAL, 0xFFFFFFFF);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_PKT_FATAL, 0xFFFFFFFF);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_FRAME_FATAL, 0xFFFFFFFF);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_PHY, 0xFFFFFFFF);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_PKT, 0xFFFFFFFF);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_LINE, 0xFFFFFFFF);
> +	dw_mipi_csi_write(dev, R_CSI2_MASK_INT_IPI, 0xFFFFFFFF);
> +
> +	return 0;
> +
> +}
> +
> +static void dw_mipi_csi_set_ipi_fmt(struct mipi_csi_dev *csi_dev)
> +{
> +	struct device *dev = &csi_dev->pdev->dev;
> +
> +	switch (csi_dev->fmt->code) {
> +	case MEDIA_BUS_FMT_RGB565_2X8_BE:
> +	case MEDIA_BUS_FMT_RGB565_2X8_LE:
> +		dw_mipi_csi_write(csi_dev, R_CSI2_IPI_DATA_TYPE, CSI_2_RGB565);
> +		dev_dbg(dev, "DT: RGB 565");
> +		break;
> +
> +	case MEDIA_BUS_FMT_RGB888_2X12_LE:
> +	case MEDIA_BUS_FMT_RGB888_2X12_BE:
> +		dw_mipi_csi_write(csi_dev, R_CSI2_IPI_DATA_TYPE, CSI_2_RGB888);
> +		dev_dbg(dev, "DT: RGB 888");
> +		break;
> +	case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE:
> +		dw_mipi_csi_write(csi_dev, R_CSI2_IPI_DATA_TYPE, CSI_2_RAW10);
> +		dev_dbg(dev, "DT: RAW 10");
> +		break;
> +	case MEDIA_BUS_FMT_SBGGR8_1X8:
> +		dw_mipi_csi_write(csi_dev, R_CSI2_IPI_DATA_TYPE, CSI_2_RAW8);
> +		dev_dbg(dev, "DT: RAW 8");
> +		break;
> +	default:
> +		dw_mipi_csi_write(csi_dev, R_CSI2_IPI_DATA_TYPE, CSI_2_RGB565);
> +		dev_dbg(dev, "Error");
> +		break;
> +	}
> +}
> +
> +static void __dw_mipi_csi_fill_timings(struct mipi_csi_dev *dev,
> +			   const struct v4l2_bt_timings *bt)
> +{
> +
> +	if (bt == NULL)
> +		return;
> +
> +	dev->hw.hsa = bt->hsync;
> +	dev->hw.hbp = bt->hbackporch;
> +	dev->hw.hsd = bt->hsync;
> +	dev->hw.htotal = bt->height + bt->vfrontporch +
> +	    bt->vsync + bt->vbackporch;
> +	dev->hw.vsa = bt->vsync;
> +	dev->hw.vbp = bt->vbackporch;
> +	dev->hw.vfp = bt->vfrontporch;
> +	dev->hw.vactive = bt->height;
> +}
> +
> +static void dw_mipi_csi_start(struct mipi_csi_dev *csi_dev)
> +{
> +	const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[0].bt;
> +	struct device *dev = &csi_dev->pdev->dev;
> +
> +	__dw_mipi_csi_fill_timings(csi_dev, bt);
> +
> +	dw_mipi_csi_write(csi_dev, R_CSI2_N_LANES, (csi_dev->hw.num_lanes - 1));
> +	dev_dbg(dev, "N Lanes: %d\n", csi_dev->hw.num_lanes);
> +
> +	/*IPI Related Configuration */
> +	if ((csi_dev->hw.output_type == IPI_OUT)
> +	    || (csi_dev->hw.output_type == BOTH_OUT)) {
> +
> +		dw_mipi_csi_write_part(csi_dev, R_CSI2_IPI_MODE,
> +					csi_dev->hw.ipi_mode, 0, 1);
> +		dev_dbg(dev, "IPI MODE: %d\n", csi_dev->hw.ipi_mode);
> +
> +		dw_mipi_csi_write_part(csi_dev, R_CSI2_IPI_MODE,
> +				       csi_dev->hw.ipi_color_mode, 8, 1);
> +		dev_dbg(dev, "Color Mode: %d\n", csi_dev->hw.ipi_color_mode);
> +
> +		dw_mipi_csi_write(csi_dev, R_CSI2_IPI_VCID,
> +					csi_dev->hw.virtual_ch);
> +		dev_dbg(dev, "Virtual Channel: %d\n", csi_dev->hw.virtual_ch);
> +
> +		dw_mipi_csi_write_part(csi_dev, R_CSI2_IPI_MEM_FLUSH,
> +				       csi_dev->hw.ipi_auto_flush, 8, 1);
> +		dev_dbg(dev, "Auto-flush: %d\n", csi_dev->hw.ipi_auto_flush);
> +
> +		dw_mipi_csi_write(csi_dev, R_CSI2_IPI_HSA_TIME,
> +					csi_dev->hw.hsa);
> +		dev_dbg(dev, "HSA: %d\n", csi_dev->hw.hsa);
> +
> +		dw_mipi_csi_write(csi_dev, R_CSI2_IPI_HBP_TIME,
> +					csi_dev->hw.hbp);
> +		dev_dbg(dev, "HBP: %d\n", csi_dev->hw.hbp);
> +
> +		dw_mipi_csi_write(csi_dev, R_CSI2_IPI_HSD_TIME,
> +					csi_dev->hw.hsd);
> +		dev_dbg(dev, "HSD: %d\n", csi_dev->hw.hsd);
> +
> +		if (csi_dev->hw.ipi_mode == AUTO_TIMING) {
> +			dw_mipi_csi_write(csi_dev, R_CSI2_IPI_HLINE_TIME,
> +					  csi_dev->hw.htotal);
> +			dev_dbg(dev, "H total: %d\n", csi_dev->hw.htotal);
> +
> +			dw_mipi_csi_write(csi_dev, R_CSI2_IPI_VSA_LINES,
> +					  csi_dev->hw.vsa);
> +			dev_dbg(dev, "VSA: %d\n", csi_dev->hw.vsa);
> +
> +			dw_mipi_csi_write(csi_dev, R_CSI2_IPI_VBP_LINES,
> +					  csi_dev->hw.vbp);
> +			dev_dbg(dev, "VBP: %d\n", csi_dev->hw.vbp);
> +
> +			dw_mipi_csi_write(csi_dev, R_CSI2_IPI_VFP_LINES,
> +					  csi_dev->hw.vfp);
> +			dev_dbg(dev, "VFP: %d\n", csi_dev->hw.vfp);
> +
> +			dw_mipi_csi_write(csi_dev, R_CSI2_IPI_VACTIVE_LINES,
> +					  csi_dev->hw.vactive);
> +			dev_dbg(dev, "V Active: %d\n", csi_dev->hw.vactive);
> +		}
> +	}
> +
> +	phy_power_on(csi_dev->phy);
> +}
> +
> +static int dw_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
> +					struct v4l2_subdev_pad_config *cfg,
> +					struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	if (code->index >= ARRAY_SIZE(dw_mipi_csi_formats))
> +		return -EINVAL;
> +
> +	code->code = dw_mipi_csi_formats[code->index].code;
> +	return 0;
> +}
> +
> +static struct mipi_fmt const *
> +dw_mipi_csi_try_format(struct v4l2_mbus_framefmt *mf)
> +{
> +	struct mipi_fmt const *fmt;
> +
> +	fmt = find_dw_mipi_csi_format(mf);
> +	if (fmt == NULL)
> +		fmt = &dw_mipi_csi_formats[0];
> +
> +	mf->code = fmt->code;
> +	return fmt;
> +}
> +
> +static struct v4l2_mbus_framefmt *
> +__dw_mipi_csi_get_format(struct mipi_csi_dev *dev,
> +			 struct v4l2_subdev_pad_config *cfg,
> +			 enum v4l2_subdev_format_whence which)
> +{
> +	if (which == V4L2_SUBDEV_FORMAT_TRY)
> +		return cfg ? v4l2_subdev_get_try_format(&dev->sd, cfg,
> +							0) : NULL;
> +
> +	return &dev->format;
> +}
> +
> +static int
> +dw_mipi_csi_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
> +		    struct v4l2_subdev_format *fmt)
> +{
> +	struct mipi_csi_dev *dev = sd_to_mipi_csi_dev(sd);
> +	struct mipi_fmt const *dev_fmt;
> +	struct v4l2_mbus_framefmt *mf;
> +	unsigned int i = 0;
> +	const struct v4l2_bt_timings *bt_r = &v4l2_dv_timings_presets[0].bt;
> +
> +	mf = __dw_mipi_csi_get_format(dev, cfg, fmt->which);
> +
> +	dev_fmt = dw_mipi_csi_try_format(&fmt->format);
> +	if (dev_fmt) {
> +		*mf = fmt->format;
> +		if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
> +			dev->fmt = dev_fmt;
> +		dw_mipi_csi_set_ipi_fmt(dev);
> +	}
> +	while (v4l2_dv_timings_presets[i].bt.width) {
> +		const struct v4l2_bt_timings *bt =
> +		    &v4l2_dv_timings_presets[i].bt;
> +		if (mf->width == bt->width && mf->height == bt->width) {
> +			__dw_mipi_csi_fill_timings(dev, bt);
> +			return 0;
> +		}
> +		i++;
> +	}
> +
> +	__dw_mipi_csi_fill_timings(dev, bt_r);

This code is weird. The video source can be either from a sensor or from an
HDMI input, right?

But if it is from a sensor, then using v4l2_dv_timings_presets since that's for
an HDMI input. Sensors will typically not follow these preset timings.

For HDMI input I expect that this driver supports the s_dv_timings op and will
just use the timings set there and override the width/height in v4l2_subdev_format.

For sensors I am actually not quite certain how this is done. I've CC-ed Sakari
since he'll know. But let us know first whether it is indeed the intention that
this should also work with a sensor.

> +	return 0;
> +
> +}
> +
> +static int
> +dw_mipi_csi_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
> +		    struct v4l2_subdev_format *fmt)
> +{
> +	struct mipi_csi_dev *dev = sd_to_mipi_csi_dev(sd);
> +	struct v4l2_mbus_framefmt *mf;
> +
> +	mf = __dw_mipi_csi_get_format(dev, cfg, fmt->which);
> +	if (!mf)
> +		return -EINVAL;
> +
> +	mutex_lock(&dev->lock);
> +	fmt->format = *mf;
> +	mutex_unlock(&dev->lock);
> +	return 0;
> +}
> +
> +static int
> +dw_mipi_csi_s_power(struct v4l2_subdev *sd, int on)
> +{
> +	struct mipi_csi_dev *dev = sd_to_mipi_csi_dev(sd);
> +
> +	if (on) {
> +		dw_mipi_csi_hw_stdby(dev);
> +		dw_mipi_csi_start(dev);
> +	} else {
> +		dw_mipi_csi_mask_irq_power_off(dev);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +dw_mipi_csi_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> +{
> +	struct v4l2_mbus_framefmt *format =
> +	    v4l2_subdev_get_try_format(sd, fh->pad, 0);
> +
> +	format->colorspace = V4L2_COLORSPACE_SRGB;
> +	format->code = dw_mipi_csi_formats[0].code;
> +	format->width = MIN_WIDTH;
> +	format->height = MIN_HEIGHT;
> +	format->field = V4L2_FIELD_NONE;

Don't do this. Instead implement the init_cfg pad op and initialize this there.

You can then drop this function.

> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_internal_ops dw_mipi_csi_sd_internal_ops = {
> +	.open = dw_mipi_csi_open,
> +};
> +
> +static struct v4l2_subdev_core_ops dw_mipi_csi_core_ops = {
> +	.s_power = dw_mipi_csi_s_power,
> +};
> +
> +static struct v4l2_subdev_pad_ops dw_mipi_csi_pad_ops = {
> +	.enum_mbus_code = dw_mipi_csi_enum_mbus_code,
> +	.get_fmt = dw_mipi_csi_get_fmt,
> +	.set_fmt = dw_mipi_csi_set_fmt,
> +};
> +
> +static struct v4l2_subdev_ops dw_mipi_csi_subdev_ops = {
> +	.core = &dw_mipi_csi_core_ops,
> +	.pad = &dw_mipi_csi_pad_ops,
> +};
> +
> +static irqreturn_t
> +dw_mipi_csi_irq1(int irq, void *dev_id)
> +{
> +	struct mipi_csi_dev *csi_dev = dev_id;
> +	u32 global_int_status, i_sts;
> +	unsigned long flags;
> +	struct device *dev = &csi_dev->pdev->dev;
> +
> +	global_int_status = dw_mipi_csi_read(csi_dev, R_CSI2_INTERRUPT);
> +	spin_lock_irqsave(&csi_dev->slock, flags);
> +
> +	if (global_int_status & CSI2_INT_PHY_FATAL) {
> +		i_sts = dw_mipi_csi_read(csi_dev, R_CSI2_INT_PHY_FATAL);
> +		dev_dbg_ratelimited(dev, "CSI INT PHY FATAL: %08X\n", i_sts);
> +	}
> +
> +	if (global_int_status & CSI2_INT_PKT_FATAL) {
> +		i_sts = dw_mipi_csi_read(csi_dev, R_CSI2_INT_PKT_FATAL);
> +		dev_dbg_ratelimited(dev, "CSI INT PKT FATAL: %08X\n", i_sts);
> +	}
> +
> +	if (global_int_status & CSI2_INT_FRAME_FATAL) {
> +		i_sts = dw_mipi_csi_read(csi_dev, R_CSI2_INT_FRAME_FATAL);
> +		dev_dbg_ratelimited(dev, "CSI INT FRAME FATAL: %08X\n", i_sts);
> +	}
> +
> +	if (global_int_status & CSI2_INT_PHY) {
> +		i_sts = dw_mipi_csi_read(csi_dev, R_CSI2_INT_PHY);
> +		dev_dbg_ratelimited(dev, "CSI INT PHY: %08X\n", i_sts);
> +	}
> +
> +	if (global_int_status & CSI2_INT_PKT) {
> +		i_sts = dw_mipi_csi_read(csi_dev, R_CSI2_INT_PKT);
> +		dev_dbg_ratelimited(dev, "CSI INT PKT: %08X\n", i_sts);
> +	}
> +
> +	if (global_int_status & CSI2_INT_LINE) {
> +		i_sts = dw_mipi_csi_read(csi_dev, R_CSI2_INT_LINE);
> +		dev_dbg_ratelimited(dev, "CSI INT LINE: %08X\n", i_sts);
> +	}
> +
> +	if (global_int_status & CSI2_INT_IPI) {
> +		i_sts = dw_mipi_csi_read(csi_dev, R_CSI2_INT_IPI);
> +		dev_dbg_ratelimited(dev, "CSI INT IPI: %08X\n", i_sts);
> +	}
> +	spin_unlock_irqrestore(&csi_dev->slock, flags);
> +	return IRQ_HANDLED;
> +}
> +
> +static int
> +dw_mipi_csi_parse_dt(struct platform_device *pdev, struct mipi_csi_dev *dev)
> +{
> +	struct device_node *node = pdev->dev.of_node;
> +	int reg;
> +	int ret = 0;
> +
> +	/* Device tree information */

I would expect to see a call to v4l2_of_parse_endpoint here.

> +	ret = of_property_read_u32(node, "data-lanes", &dev->hw.num_lanes);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Couldn't read data-lanes\n");
> +		return ret;
> +	}
> +
> +	ret = of_property_read_u32(node, "output-type", &dev->hw.output_type);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Couldn't read output-type\n");
> +		return ret;
> +	}
> +
> +	ret = of_property_read_u32(node, "ipi-mode", &dev->hw.ipi_mode);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Couldn't read ipi-mode\n");
> +		return ret;
> +	}
> +
> +	ret =
> +	    of_property_read_u32(node, "ipi-auto-flush",
> +				 &dev->hw.ipi_auto_flush);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Couldn't read ipi-auto-flush\n");
> +		return ret;
> +	}
> +
> +	ret =
> +	    of_property_read_u32(node, "ipi-color-mode",
> +				 &dev->hw.ipi_color_mode);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Couldn't read ipi-color-mode\n");
> +		return ret;
> +	}
> +
> +	ret =
> +	    of_property_read_u32(node, "virtual-channel", &dev->hw.virtual_ch);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Couldn't read virtual-channel\n");
> +		return ret;
> +	}
> +
> +	node = of_get_child_by_name(node, "port");
> +	if (!node)
> +		return -EINVAL;
> +
> +	ret = of_property_read_u32(node, "reg", &reg);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Couldn't read reg value\n");
> +		return ret;
> +	}
> +	dev->index = reg - 1;
> +
> +	if (dev->index >= CSI_MAX_ENTITIES)
> +		return -ENXIO;
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id dw_mipi_csi_of_match[];
> +
> +/**
> + * @short Initialization routine - Entry point of the driver
> + * @param[in] pdev pointer to the platform device structure
> + * @return 0 on success and a negative number on failure
> + * Refer to Linux errors.
> + */
> +static int mipi_csi_probe(struct platform_device *pdev)
> +{
> +	const struct of_device_id *of_id;
> +	struct device *dev = &pdev->dev;
> +	struct resource *res = NULL;
> +	struct mipi_csi_dev *mipi_csi;
> +	int ret = -ENOMEM;
> +
> +	mipi_csi = devm_kzalloc(dev, sizeof(*mipi_csi), GFP_KERNEL);
> +	if (!dev)
> +		return -ENOMEM;
> +
> +	mutex_init(&mipi_csi->lock);
> +	spin_lock_init(&mipi_csi->slock);
> +	mipi_csi->pdev = pdev;
> +
> +	of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
> +	if (WARN_ON(of_id == NULL))
> +		return -EINVAL;
> +
> +	ret = dw_mipi_csi_parse_dt(pdev, mipi_csi);
> +	if (ret < 0)
> +		return ret;
> +
> +	mipi_csi->phy = devm_phy_get(dev, "csi2-dphy");
> +	if (IS_ERR(mipi_csi->phy)) {
> +		dev_err(dev, "No DPHY available\n");
> +		return PTR_ERR(mipi_csi->phy);
> +	}
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	mipi_csi->base_address = devm_ioremap_resource(dev, res);
> +
> +	if (IS_ERR(mipi_csi->base_address)) {
> +		dev_err(dev, "Base address not set.\n");
> +		return PTR_ERR(mipi_csi->base_address);
> +	}
> +
> +	mipi_csi->ctrl_irq_number = platform_get_irq(pdev, 0);
> +	if (mipi_csi->ctrl_irq_number <= 0) {
> +		dev_err(dev, "IRQ number not set.\n");
> +		return mipi_csi->ctrl_irq_number;
> +	}
> +
> +	ret = devm_request_irq(dev, mipi_csi->ctrl_irq_number,
> +			       dw_mipi_csi_irq1, IRQF_SHARED,
> +			       dev_name(dev), mipi_csi);
> +	if (ret) {
> +		dev_err(dev, "IRQ failed\n");
> +		goto end;
> +	}
> +
> +	v4l2_subdev_init(&mipi_csi->sd, &dw_mipi_csi_subdev_ops);
> +	mipi_csi->sd.owner = THIS_MODULE;
> +	snprintf(mipi_csi->sd.name, sizeof(mipi_csi->sd.name), "%s.%d",
> +		 CSI_DEVICE_NAME, mipi_csi->index);
> +	mipi_csi->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	mipi_csi->fmt = &dw_mipi_csi_formats[0];
> +
> +	mipi_csi->format.code = dw_mipi_csi_formats[0].code;
> +	mipi_csi->format.width = MIN_WIDTH;
> +	mipi_csi->format.height = MIN_HEIGHT;
> +
> +	mipi_csi->sd.entity.function = MEDIA_ENT_F_IO_V4L;
> +	mipi_csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	mipi_csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&mipi_csi->sd.entity,
> +				     CSI_PADS_NUM, mipi_csi->pads);
> +
> +	if (ret < 0) {
> +		dev_err(dev, "Media Entity init failed\n");
> +		goto entity_cleanup;
> +	}
> +
> +	/* This allows to retrieve the platform device id by the host driver */
> +	v4l2_set_subdevdata(&mipi_csi->sd, pdev);
> +
> +	/* .. and a pointer to the subdev. */
> +	platform_set_drvdata(pdev, &mipi_csi->sd);
> +
> +	dw_mipi_csi_mask_irq_power_off(mipi_csi);
> +	dev_info(dev, "DW MIPI CSI-2 Host registered successfully\n");
> +	return 0;
> +
> +entity_cleanup:
> +	media_entity_cleanup(&mipi_csi->sd.entity);
> +end:
> +	return ret;
> +}
> +
> +/**
> + * @short Exit routine - Exit point of the driver
> + * @param[in] pdev pointer to the platform device structure
> + * @return 0 on success and a negative number on failure
> + * Refer to Linux errors.
> + */
> +static int mipi_csi_remove(struct platform_device *pdev)
> +{
> +	struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> +	struct mipi_csi_dev *mipi_csi = sd_to_mipi_csi_dev(sd);
> +
> +	dev_dbg(&pdev->dev, "Removing MIPI CSI-2 module\n");
> +	media_entity_cleanup(&mipi_csi->sd.entity);
> +
> +	return 0;
> +}
> +
> +/**
> + * @short of_device_id structure
> + */
> +static const struct of_device_id dw_mipi_csi_of_match[] = {
> +	{
> +	 .compatible = "snps,dw-mipi-csi"},
> +	{ /* sentinel */ },
> +};
> +
> +MODULE_DEVICE_TABLE(of, dw_mipi_csi_of_match);
> +
> +/**
> + * @short Platform driver structure
> + */
> +static struct platform_driver __refdata dw_mipi_csi_pdrv = {
> +	.remove = mipi_csi_remove,
> +	.probe = mipi_csi_probe,
> +	.driver = {
> +		   .name = CSI_DEVICE_NAME,
> +		   .owner = THIS_MODULE,
> +		   .of_match_table = dw_mipi_csi_of_match,
> +		   },
> +};
> +
> +module_platform_driver(dw_mipi_csi_pdrv);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Ramiro Oliveira <roliveir-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>");
> +MODULE_DESCRIPTION("Synopsys DW MIPI CSI-2 Host driver");
> diff --git a/drivers/media/platform/dwc/dw_mipi_csi.h b/drivers/media/platform/dwc/dw_mipi_csi.h
> new file mode 100644
> index 0000000..610a01f
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw_mipi_csi.h
> @@ -0,0 +1,180 @@
> +/*
> + * Copyright (C) 2016 Synopsys, Inc. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef DW_MIPI_CSI_H_
> +#define DW_MIPI_CSI_H_
> +
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/io.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_graph.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/ratelimit.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +#include <linux/wait.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-dv-timings.h>
> +
> +#include "plat_ipk_video.h"
> +
> +#define CSI_DEVICE_NAME	"dw-mipi-csi"
> +
> +/** @short DWC MIPI CSI-2 register addresses*/
> +enum register_addresses {
> +	R_CSI2_VERSION = 0x00,
> +	R_CSI2_N_LANES = 0x04,
> +	R_CSI2_CTRL_RESETN = 0x08,
> +	R_CSI2_INTERRUPT = 0x0C,
> +	R_CSI2_DATA_IDS_1 = 0x10,
> +	R_CSI2_DATA_IDS_2 = 0x14,
> +	R_CSI2_IPI_MODE = 0x80,
> +	R_CSI2_IPI_VCID = 0x84,
> +	R_CSI2_IPI_DATA_TYPE = 0x88,
> +	R_CSI2_IPI_MEM_FLUSH = 0x8C,
> +	R_CSI2_IPI_HSA_TIME = 0x90,
> +	R_CSI2_IPI_HBP_TIME = 0x94,
> +	R_CSI2_IPI_HSD_TIME = 0x98,
> +	R_CSI2_IPI_HLINE_TIME = 0x9C,
> +	R_CSI2_IPI_VSA_LINES = 0xB0,
> +	R_CSI2_IPI_VBP_LINES = 0xB4,
> +	R_CSI2_IPI_VFP_LINES = 0xB8,
> +	R_CSI2_IPI_VACTIVE_LINES = 0xBC,
> +	R_CSI2_INT_PHY_FATAL = 0xe0,
> +	R_CSI2_MASK_INT_PHY_FATAL = 0xe4,
> +	R_CSI2_FORCE_INT_PHY_FATAL = 0xe8,
> +	R_CSI2_INT_PKT_FATAL = 0xf0,
> +	R_CSI2_MASK_INT_PKT_FATAL = 0xf4,
> +	R_CSI2_FORCE_INT_PKT_FATAL = 0xf8,
> +	R_CSI2_INT_FRAME_FATAL = 0x100,
> +	R_CSI2_MASK_INT_FRAME_FATAL = 0x104,
> +	R_CSI2_FORCE_INT_FRAME_FATAL = 0x108,
> +	R_CSI2_INT_PHY = 0x110,
> +	R_CSI2_MASK_INT_PHY = 0x114,
> +	R_CSI2_FORCE_INT_PHY = 0x118,
> +	R_CSI2_INT_PKT = 0x120,
> +	R_CSI2_MASK_INT_PKT = 0x124,
> +	R_CSI2_FORCE_INT_PKT = 0x128,
> +	R_CSI2_INT_LINE = 0x130,
> +	R_CSI2_MASK_INT_LINE = 0x134,
> +	R_CSI2_FORCE_INT_LINE = 0x138,
> +	R_CSI2_INT_IPI = 0x140,
> +	R_CSI2_MASK_INT_IPI = 0x144,
> +	R_CSI2_FORCE_INT_IPI = 0x148
> +};
> +
> +/** @short IPI Data Types */
> +enum data_type {
> +	CSI_2_YUV420_8 = 0x18,
> +	CSI_2_YUV420_10 = 0x19,
> +	CSI_2_YUV420_8_LEG = 0x1A,
> +	CSI_2_YUV420_8_SHIFT = 0x1C,
> +	CSI_2_YUV420_10_SHIFT = 0x1D,
> +	CSI_2_YUV422_8 = 0x1E,
> +	CSI_2_YUV422_10 = 0x1F,
> +	CSI_2_RGB444 = 0x20,
> +	CSI_2_RGB555 = 0x21,
> +	CSI_2_RGB565 = 0x22,
> +	CSI_2_RGB666 = 0x23,
> +	CSI_2_RGB888 = 0x24,
> +	CSI_2_RAW6 = 0x28,
> +	CSI_2_RAW7 = 0x29,
> +	CSI_2_RAW8 = 0x2A,
> +	CSI_2_RAW10 = 0x2B,
> +	CSI_2_RAW12 = 0x2C,
> +	CSI_2_RAW14 = 0x2D,
> +};
> +
> +/** @short Interrupt Masks */
> +enum interrupt_type {
> +	CSI2_INT_PHY_FATAL = 1 << 0,
> +	CSI2_INT_PKT_FATAL = 1 << 1,
> +	CSI2_INT_FRAME_FATAL = 1 << 2,
> +	CSI2_INT_PHY = 1 << 16,
> +	CSI2_INT_PKT = 1 << 17,
> +	CSI2_INT_LINE = 1 << 18,
> +	CSI2_INT_IPI = 1 << 19,
> +
> +};
> +
> +/** @short DWC MIPI CSI-2 output types*/
> +enum output_type {
> +	IPI_OUT = 0,
> +	IDI_OUT = 1,
> +	BOTH_OUT = 2
> +};
> +
> +/** @short IPI output types*/
> +enum ipi_output_type {
> +	CAMERA_TIMING = 0,
> +	AUTO_TIMING = 1
> +};
> +
> +/**
> + * @short Format template
> + */
> +struct mipi_fmt {
> +	u32 code;
> +	u8 depth;
> +};
> +
> +struct csi_hw {
> +
> +	uint32_t num_lanes;
> +	uint32_t output_type;
> +
> +	/*IPI Info */
> +	uint32_t ipi_mode;
> +	uint32_t ipi_color_mode;
> +	uint32_t ipi_auto_flush;
> +	uint32_t virtual_ch;
> +
> +	uint32_t hsa;
> +	uint32_t hbp;
> +	uint32_t hsd;
> +	uint32_t htotal;
> +
> +	uint32_t vsa;
> +	uint32_t vbp;
> +	uint32_t vfp;
> +	uint32_t vactive;
> +};
> +
> +/**
> + * @short Structure to embed device driver information
> + */
> +struct mipi_csi_dev {
> +	struct v4l2_subdev sd;
> +	struct video_device vdev;
> +
> +	struct mutex lock;
> +	spinlock_t slock;
> +	struct media_pad pads[CSI_PADS_NUM];
> +	struct platform_device *pdev;
> +	u8 index;
> +
> +	/** Store current format */
> +	const struct mipi_fmt *fmt;
> +	struct v4l2_mbus_framefmt format;
> +
> +	/** Device Tree Information */
> +	void __iomem *base_address;
> +	uint32_t ctrl_irq_number;
> +
> +	struct csi_hw hw;
> +
> +	struct phy *phy;
> +};
> +
> +#endif				/* DW_MIPI_CSI */
> diff --git a/drivers/media/platform/dwc/plat_ipk.c b/drivers/media/platform/dwc/plat_ipk.c
> new file mode 100644
> index 0000000..02dcf36
> --- /dev/null
> +++ b/drivers/media/platform/dwc/plat_ipk.c
> @@ -0,0 +1,818 @@
> +/**
> + * DWC MIPI CSI-2 Host IPK platform device driver

What does IPK stand for?

> + *
> + * Based on Omnivision OV7670 Camera Driver
> + * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd.
> + * Author: Sylwester Nawrocki <s.nawrocki-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> + *
> + * Copyright (C) 2016 Synopsys, Inc. All rights reserved.
> + * Author: Ramiro Oliveira <ramiro.oliveira-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published
> + * by the Free Software Foundation, either version 2 of the License,
> + * or (at your option) any later version.
> + */
> +
> +#include "plat_ipk.h"
> +
> +static int
> +__plat_ipk_pipeline_s_format(struct plat_ipk_media_pipeline *ep,
> +			     struct v4l2_subdev_format *fmt)
> +{
> +
> +	struct plat_ipk_pipeline *p = to_plat_ipk_pipeline(ep);
> +	static const u8 seq[IDX_MAX] = {IDX_SENSOR, IDX_CSI, IDX_VDEV};
> +
> +	fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE;
> +	v4l2_subdev_call(p->subdevs[seq[IDX_CSI]], pad, set_fmt, NULL, fmt);
> +
> +	return 0;
> +}
> +
> +static void
> +plat_ipk_pipeline_prepare(struct plat_ipk_pipeline *p, struct media_entity *me)
> +{
> +	struct v4l2_subdev *sd;
> +	unsigned int i = 0;
> +
> +	for (i = 0; i < IDX_MAX; i++)
> +		p->subdevs[i] = NULL;
> +
> +	while (1) {
> +		struct media_pad *pad = NULL;
> +
> +		for (i = 0; i < me->num_pads; i++) {
> +			struct media_pad *spad = &me->pads[i];
> +
> +			if (!(spad->flags & MEDIA_PAD_FL_SINK))
> +				continue;
> +
> +			pad = media_entity_remote_pad(spad);
> +			if (pad)
> +				break;
> +		}
> +		if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
> +			break;
> +
> +		sd = media_entity_to_v4l2_subdev(pad->entity);
> +
> +		switch (sd->grp_id) {
> +		case GRP_ID_SENSOR:
> +			p->subdevs[IDX_SENSOR] = sd;
> +			break;
> +		case GRP_ID_CSI:
> +			p->subdevs[IDX_CSI] = sd;
> +			break;
> +		case GRP_ID_VIDEODEV:
> +			p->subdevs[IDX_VDEV] = sd;
> +			break;
> +		default:
> +			break;
> +		}
> +		me = &sd->entity;
> +		if (me->num_pads == 1)
> +			break;
> +	}
> +}
> +
> +static int __subdev_set_power(struct v4l2_subdev *sd, int on)
> +{
> +	int *use_count;
> +	int ret;
> +
> +	if (sd == NULL) {
> +		pr_err("null subdev\n");
> +		return -ENXIO;
> +	}
> +	use_count = &sd->entity.use_count;
> +	if (on && (*use_count)++ > 0)
> +		return 0;
> +	else if (!on && (*use_count == 0 || --(*use_count) > 0))
> +		return 0;
> +
> +	ret = v4l2_subdev_call(sd, core, s_power, on);
> +
> +	return ret != -ENOIOCTLCMD ? ret : 0;
> +}
> +
> +static int plat_ipk_pipeline_s_power(struct plat_ipk_pipeline *p, bool on)
> +{
> +	static const u8 seq[IDX_MAX] = {IDX_CSI, IDX_SENSOR, IDX_VDEV};
> +	int i, ret = 0;
> +
> +	for (i = 0; i < IDX_MAX; i++) {
> +		unsigned int idx = seq[i];
> +
> +		if (p->subdevs[idx] == NULL)
> +			pr_info("No device registered on %d\n", idx);
> +		else {
> +			ret = __subdev_set_power(p->subdevs[idx], on);
> +			if (ret < 0 && ret != -ENXIO)
> +				goto error;
> +		}
> +	}
> +	return 0;
> +error:
> +	for (; i >= 0; i--) {
> +		unsigned int idx = seq[i];
> +
> +		__subdev_set_power(p->subdevs[idx], !on);
> +	}
> +	return ret;
> +}
> +
> +static int
> +__plat_ipk_pipeline_open(struct plat_ipk_media_pipeline *ep,
> +			 struct media_entity *me, bool prepare)
> +{
> +	struct plat_ipk_pipeline *p = to_plat_ipk_pipeline(ep);
> +	int ret;
> +
> +	if (WARN_ON(p == NULL || me == NULL))
> +		return -EINVAL;
> +
> +	if (prepare)
> +		plat_ipk_pipeline_prepare(p, me);
> +
> +	ret = plat_ipk_pipeline_s_power(p, 1);
> +	if (!ret)
> +		return 0;
> +
> +	return ret;
> +}
> +
> +static int __plat_ipk_pipeline_close(struct plat_ipk_media_pipeline *ep)
> +{
> +	struct plat_ipk_pipeline *p = to_plat_ipk_pipeline(ep);
> +	int ret;
> +
> +	ret = plat_ipk_pipeline_s_power(p, 0);
> +
> +	return ret == -ENXIO ? 0 : ret;
> +}
> +
> +static int
> +__plat_ipk_pipeline_s_stream(struct plat_ipk_media_pipeline *ep, bool on)
> +{
> +	static const u8 seq[IDX_MAX] = {IDX_SENSOR, IDX_CSI, IDX_VDEV};
> +	struct plat_ipk_pipeline *p = to_plat_ipk_pipeline(ep);
> +	int i, ret = 0;
> +
> +	for (i = 0; i < IDX_MAX; i++) {
> +		unsigned int idx = seq[i];
> +
> +		if (p->subdevs[idx] == NULL)
> +			pr_debug("No device registered on %d\n", idx);
> +		else {
> +			ret =
> +			    v4l2_subdev_call(p->subdevs[idx], video, s_stream,
> +					     on);
> +
> +			if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
> +				goto error;
> +		}
> +	}
> +	return 0;
> +error:
> +	for (; i >= 0; i--) {
> +		unsigned int idx = seq[i];
> +
> +		v4l2_subdev_call(p->subdevs[idx], video, s_stream, !on);
> +	}
> +	return ret;
> +}
> +
> +static const struct plat_ipk_media_pipeline_ops plat_ipk_pipeline_ops = {
> +	.open = __plat_ipk_pipeline_open,
> +	.close = __plat_ipk_pipeline_close,
> +	.set_format = __plat_ipk_pipeline_s_format,
> +	.set_stream = __plat_ipk_pipeline_s_stream,
> +};
> +
> +static struct plat_ipk_media_pipeline *
> +plat_ipk_pipeline_create(struct plat_ipk_dev *plat_ipk)
> +{
> +	struct plat_ipk_pipeline *p;
> +
> +	p = kzalloc(sizeof(*p), GFP_KERNEL);
> +	if (!p)
> +		return NULL;
> +
> +	list_add_tail(&p->list, &plat_ipk->pipelines);
> +
> +	p->ep.ops = &plat_ipk_pipeline_ops;
> +	return &p->ep;
> +}
> +
> +static void
> +plat_ipk_pipelines_free(struct plat_ipk_dev *plat_ipk)
> +{
> +	while (!list_empty(&plat_ipk->pipelines)) {
> +		struct plat_ipk_pipeline *p;
> +
> +		p = list_entry(plat_ipk->pipelines.next, typeof(*p), list);
> +		list_del(&p->list);
> +		kfree(p);
> +	}
> +}
> +
> +static int
> +plat_ipk_parse_port_node(struct plat_ipk_dev *plat_ipk,
> +			 struct device_node *port, unsigned int index)
> +{
> +	struct device_node *rem, *ep;
> +	struct v4l2_of_endpoint endpoint;
> +	struct plat_ipk_source_info *pd = &plat_ipk->sensor[index].pdata;
> +
> +	/* Assume here a port node can have only one endpoint node. */
> +	ep = of_get_next_child(port, NULL);
> +	if (!ep)
> +		return 0;
> +
> +	v4l2_of_parse_endpoint(ep, &endpoint);
> +	if (WARN_ON(endpoint.base.port == 0) || index >= PLAT_MAX_SENSORS)
> +		return -EINVAL;
> +
> +	pd->mux_id = endpoint.base.port - 1;
> +
> +	rem = of_graph_get_remote_port_parent(ep);
> +	of_node_put(ep);
> +	if (rem == NULL) {
> +		dev_info(plat_ipk->dev,
> +			  "Remote device at %s not found\n", ep->full_name);
> +		return 0;
> +	}
> +
> +	if (WARN_ON(index >= ARRAY_SIZE(plat_ipk->sensor)))
> +		return -EINVAL;
> +
> +	plat_ipk->sensor[index].asd.match_type = V4L2_ASYNC_MATCH_OF;
> +	plat_ipk->sensor[index].asd.match.of.node = rem;
> +	plat_ipk->async_subdevs[index] = &plat_ipk->sensor[index].asd;
> +
> +	plat_ipk->num_sensors++;
> +
> +	of_node_put(rem);
> +	return 0;
> +}
> +
> +
> +static int plat_ipk_register_sensor_entities(struct plat_ipk_dev *plat_ipk)
> +{
> +	struct device_node *parent = plat_ipk->pdev->dev.of_node;
> +	struct device_node *node;
> +	int index = 0;
> +	int ret;
> +
> +	plat_ipk->num_sensors = 0;
> +
> +	for_each_available_child_of_node(parent, node) {
> +		struct device_node *port;
> +
> +		if (of_node_cmp(node->name, "csi2"))
> +			continue;
> +		port = of_get_next_child(node, NULL);
> +		if (!port)
> +			continue;
> +
> +		ret = plat_ipk_parse_port_node(plat_ipk, port, index);
> +		if (ret < 0)
> +			return ret;
> +		index++;
> +	}
> +	return 0;
> +}
> +
> +static int
> +__of_get_port_id(struct device_node *np)
> +{
> +	u32 reg = 0;
> +
> +	np = of_get_child_by_name(np, "port");
> +	if (!np)
> +		return -EINVAL;
> +	of_property_read_u32(np, "reg", &reg);
> +
> +	return reg - 1;
> +}
> +
> +static int register_videodev_entity(struct plat_ipk_dev *plat_ipk,
> +			 struct video_device_dev *vid_dev)
> +{
> +	struct v4l2_subdev *sd;
> +	struct plat_ipk_media_pipeline *ep;
> +	int ret;
> +
> +	sd = &vid_dev->subdev;
> +	sd->grp_id = GRP_ID_VIDEODEV;
> +
> +	ep = plat_ipk_pipeline_create(plat_ipk);
> +	if (!ep)
> +		return -ENOMEM;
> +
> +	v4l2_set_subdev_hostdata(sd, ep);
> +
> +	ret = v4l2_device_register_subdev(&plat_ipk->v4l2_dev, sd);
> +	if (!ret)
> +		plat_ipk->vid_dev = vid_dev;
> +	else
> +		v4l2_err(&plat_ipk->v4l2_dev,
> +			 "Failed to register Video Device\n");
> +	return ret;
> +}
> +
> +static int register_mipi_csi_entity(struct plat_ipk_dev *plat_ipk,
> +			 struct platform_device *pdev, struct v4l2_subdev *sd)
> +{
> +	struct device_node *node = pdev->dev.of_node;
> +	int id, ret;
> +
> +	id = node ? __of_get_port_id(node) : max(0, pdev->id);
> +
> +	if (WARN_ON(id < 0 || id >= CSI_MAX_ENTITIES))
> +		return -ENOENT;
> +
> +	if (WARN_ON(plat_ipk->mipi_csi[id].sd))
> +		return -EBUSY;
> +
> +	sd->grp_id = GRP_ID_CSI;
> +	ret = v4l2_device_register_subdev(&plat_ipk->v4l2_dev, sd);
> +
> +	if (!ret)
> +		plat_ipk->mipi_csi[id].sd = sd;
> +	else
> +		v4l2_err(&plat_ipk->v4l2_dev,
> +			 "Failed to register MIPI-CSI.%d (%d)\n", id, ret);
> +	return ret;
> +}
> +
> +static int plat_ipk_register_platform_entity(struct plat_ipk_dev *plat_ipk,
> +				struct platform_device *pdev, int plat_entity)
> +{
> +	struct device *dev = &pdev->dev;
> +	int ret = -EPROBE_DEFER;
> +	void *drvdata;
> +
> +	device_lock(dev);
> +	if (!dev->driver || !try_module_get(dev->driver->owner))
> +		goto dev_unlock;
> +
> +	drvdata = dev_get_drvdata(dev);
> +
> +	if (drvdata) {
> +		switch (plat_entity) {
> +		case IDX_VDEV:
> +			ret = register_videodev_entity(plat_ipk, drvdata);
> +			break;
> +		case IDX_CSI:
> +			ret = register_mipi_csi_entity(plat_ipk, pdev, drvdata);
> +			break;
> +		default:
> +			ret = -ENODEV;
> +		}
> +	} else
> +		dev_err(plat_ipk->dev, "%s no drvdata\n", dev_name(dev));
> +	module_put(dev->driver->owner);
> +dev_unlock:
> +	device_unlock(dev);
> +	if (ret == -EPROBE_DEFER)
> +		dev_info(plat_ipk->dev,
> +			 "deferring %s device registration\n", dev_name(dev));
> +	else if (ret < 0)
> +		dev_err(plat_ipk->dev,
> +			"%s device registration failed (%d)\n", dev_name(dev),
> +			ret);
> +	return ret;
> +}
> +
> +static int
> +plat_ipk_register_platform_entities(struct plat_ipk_dev *plat_ipk,
> +				    struct device_node *parent)
> +{
> +	struct device_node *node;
> +	int ret = 0;
> +
> +	for_each_available_child_of_node(parent, node) {
> +		struct platform_device *pdev;
> +		int plat_entity = -1;
> +
> +		pdev = of_find_device_by_node(node);
> +		if (!pdev)
> +			continue;
> +
> +		if (!strcmp(node->name, VIDEODEV_OF_NODE_NAME))
> +			plat_entity = IDX_VDEV;
> +		else if (!strcmp(node->name, CSI_OF_NODE_NAME))
> +			plat_entity = IDX_CSI;
> +
> +		if (plat_entity >= 0)
> +			ret = plat_ipk_register_platform_entity(plat_ipk, pdev,
> +								plat_entity);
> +		put_device(&pdev->dev);
> +		if (ret < 0)
> +			break;
> +	}
> +
> +	return ret;
> +}
> +
> +static void
> +plat_ipk_unregister_entities(struct plat_ipk_dev *plat_ipk)
> +{
> +	int i;
> +	struct video_device_dev *dev = plat_ipk->vid_dev;
> +
> +	if (dev == NULL)
> +		return;
> +	v4l2_device_unregister_subdev(&dev->subdev);
> +	dev->ve.pipe = NULL;
> +	plat_ipk->vid_dev = NULL;
> +
> +	for (i = 0; i < CSI_MAX_ENTITIES; i++) {
> +		if (plat_ipk->mipi_csi[i].sd == NULL)
> +			continue;
> +		v4l2_device_unregister_subdev(plat_ipk->mipi_csi[i].sd);
> +		plat_ipk->mipi_csi[i].sd = NULL;
> +	}
> +
> +	dev_info(plat_ipk->dev, "Unregistered all entities\n");
> +}
> +
> +static int
> +__plat_ipk_create_videodev_sink_links(struct plat_ipk_dev *plat_ipk,
> +				      struct media_entity *source,
> +				      int pad)
> +{
> +	struct media_entity *sink;
> +	int ret = 0;
> +
> +	if (!plat_ipk->vid_dev)
> +		return 0;
> +
> +	sink = &plat_ipk->vid_dev->subdev.entity;
> +	ret = media_create_pad_link(source, pad, sink,
> +				    CSI_PAD_SOURCE, MEDIA_LNK_FL_ENABLED);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(plat_ipk->dev, "created link [%s] -> [%s]\n",
> +		  source->name, sink->name);
> +
> +	return 0;
> +}
> +
> +
> +static int
> +__plat_ipk_create_videodev_source_links(struct plat_ipk_dev *plat_ipk)
> +{
> +	struct media_entity *source, *sink;
> +	int ret = 0;
> +
> +	struct video_device_dev *vid_dev = plat_ipk->vid_dev;
> +
> +	if (vid_dev == NULL)
> +		return -ENODEV;
> +
> +	source = &vid_dev->subdev.entity;
> +	sink = &vid_dev->ve.vdev.entity;
> +
> +	ret = media_create_pad_link(source, VIDEO_DEV_SD_PAD_SOURCE_DMA,
> +				    sink, 0, MEDIA_LNK_FL_ENABLED);
> +
> +	dev_dbg(plat_ipk->dev, "created link [%s] -> [%s]\n",
> +		  source->name, sink->name);
> +	return ret;
> +}
> +
> +static int
> +plat_ipk_create_links(struct plat_ipk_dev *plat_ipk)
> +{
> +	struct v4l2_subdev *csi_sensor[CSI_MAX_ENTITIES] = { NULL };
> +	struct v4l2_subdev *sensor, *csi;
> +	struct media_entity *source;
> +	struct plat_ipk_source_info *pdata;
> +	int i, pad, ret = 0;
> +
> +	for (i = 0; i < plat_ipk->num_sensors; i++) {
> +		if (plat_ipk->sensor[i].subdev == NULL)
> +			continue;
> +
> +		sensor = plat_ipk->sensor[i].subdev;
> +		pdata = v4l2_get_subdev_hostdata(sensor);
> +		if (!pdata)
> +			continue;
> +
> +		source = NULL;
> +
> +		csi = plat_ipk->mipi_csi[pdata->mux_id].sd;
> +		if (WARN(csi == NULL, "MIPI-CSI interface specified but	dw-mipi-csi module is not loaded!\n"))
> +			return -EINVAL;
> +
> +		pad = sensor->entity.num_pads - 1;
> +		ret = media_create_pad_link(&sensor->entity, pad,
> +					    &csi->entity, CSI_PAD_SINK,
> +					    MEDIA_LNK_FL_IMMUTABLE |
> +					    MEDIA_LNK_FL_ENABLED);
> +
> +		if (ret)
> +			return ret;
> +		dev_dbg(plat_ipk->dev, "created link [%s] -> [%s]\n",
> +			  sensor->entity.name, csi->entity.name);
> +
> +		csi_sensor[pdata->mux_id] = sensor;
> +	}
> +
> +	for (i = 0; i < CSI_MAX_ENTITIES; i++) {
> +		if (plat_ipk->mipi_csi[i].sd == NULL) {
> +			dev_info(plat_ipk->dev, "no link\n");
> +			continue;
> +		}
> +
> +		source = &plat_ipk->mipi_csi[i].sd->entity;
> +		pad = VIDEO_DEV_SD_PAD_SINK_CSI;
> +
> +		ret = __plat_ipk_create_videodev_sink_links(plat_ipk, source,
> +								pad);
> +	}
> +
> +	ret = __plat_ipk_create_videodev_source_links(plat_ipk);
> +	if (ret < 0)
> +		return ret;
> +
> +	return ret;
> +}
> +
> +static int __plat_ipk_modify_pipeline(struct media_entity *entity, bool enable)
> +{
> +	struct plat_ipk_video_entity *ve;
> +	struct plat_ipk_pipeline *p;
> +	struct video_device *vdev;
> +	int ret;
> +
> +	vdev = media_entity_to_video_device(entity);
> +
> +	if (vdev->entity.use_count == 0)
> +		return 0;
> +
> +	ve = vdev_to_plat_ipk_video_entity(vdev);
> +	p = to_plat_ipk_pipeline(ve->pipe);
> +
> +	if (enable)
> +		ret = __plat_ipk_pipeline_open(ve->pipe, entity, true);
> +	else
> +		ret = __plat_ipk_pipeline_close(ve->pipe);
> +
> +	if (ret == 0 && !enable)
> +		memset(p->subdevs, 0, sizeof(p->subdevs));
> +
> +	return ret;
> +}
> +
> +
> +static int
> +__plat_ipk_modify_pipelines(struct media_entity *entity, bool enable,
> +			    struct media_entity_graph *graph)
> +{
> +	struct media_entity *entity_err = entity;
> +	int ret;
> +
> +	media_entity_graph_walk_start(graph, entity);
> +
> +	while ((entity = media_entity_graph_walk_next(graph))) {
> +		if (!is_media_entity_v4l2_video_device(entity))
> +			continue;
> +
> +		ret = __plat_ipk_modify_pipeline(entity, enable);
> +
> +		if (ret < 0)
> +			goto err;
> +	}
> +
> +	return 0;
> +
> +err:
> +	media_entity_graph_walk_start(graph, entity_err);
> +
> +	while ((entity_err = media_entity_graph_walk_next(graph))) {
> +		if (!is_media_entity_v4l2_video_device(entity_err))
> +			continue;
> +
> +		__plat_ipk_modify_pipeline(entity_err, !enable);
> +
> +		if (entity_err == entity)
> +			break;
> +	}
> +
> +	return ret;
> +}
> +
> +static int
> +plat_ipk_link_notify(struct media_link *link, unsigned int flags,
> +		     unsigned int notification)
> +{
> +	struct media_entity_graph *graph =
> +	    &container_of(link->graph_obj.mdev, struct plat_ipk_dev,
> +			  media_dev)->link_setup_graph;
> +	struct media_entity *sink = link->sink->entity;
> +	int ret = 0;
> +
> +	if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH) {
> +		ret = media_entity_graph_walk_init(graph, link->graph_obj.mdev);
> +		if (ret)
> +			return ret;
> +		if (!(flags & MEDIA_LNK_FL_ENABLED))
> +			ret = __plat_ipk_modify_pipelines(sink, false, graph);
> +
> +	} else if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH) {
> +		if (link->flags & MEDIA_LNK_FL_ENABLED)
> +			ret = __plat_ipk_modify_pipelines(sink, true, graph);
> +		media_entity_graph_walk_cleanup(graph);
> +	}
> +
> +	return ret ? -EPIPE : 0;
> +}
> +
> +static const struct media_device_ops plat_ipk_media_ops = {
> +	.link_notify = plat_ipk_link_notify,
> +};
> +
> +
> +static int
> +subdev_notifier_bound(struct v4l2_async_notifier *notifier,
> +		      struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd)
> +{
> +	struct plat_ipk_dev *plat_ipk = notifier_to_plat_ipk(notifier);
> +	struct plat_ipk_sensor_info *si = NULL;
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(plat_ipk->sensor); i++)
> +		if (plat_ipk->sensor[i].asd.match.of.node ==
> +		    subdev->dev->of_node)
> +			si = &plat_ipk->sensor[i];
> +
> +	if (si == NULL)
> +		return -EINVAL;
> +
> +	v4l2_set_subdev_hostdata(subdev, &si->pdata);
> +
> +	subdev->grp_id = GRP_ID_SENSOR;
> +
> +	si->subdev = subdev;
> +
> +	dev_dbg(&plat_ipk->pdev->dev, "Registered sensor subdevice: %s (%d)\n",
> +		  subdev->name, plat_ipk->num_sensors);
> +
> +	plat_ipk->num_sensors++;
> +
> +	return 0;
> +}
> +
> +static int
> +subdev_notifier_complete(struct v4l2_async_notifier *notifier)
> +{
> +	struct plat_ipk_dev *plat_ipk = notifier_to_plat_ipk(notifier);
> +	int ret;
> +
> +	mutex_lock(&plat_ipk->media_dev.graph_mutex);
> +
> +	ret = plat_ipk_create_links(plat_ipk);
> +	if (ret < 0)
> +		goto unlock;
> +
> +	ret = v4l2_device_register_subdev_nodes(&plat_ipk->v4l2_dev);
> +unlock:
> +	mutex_unlock(&plat_ipk->media_dev.graph_mutex);
> +	if (ret < 0)
> +		return ret;
> +
> +	return media_device_register(&plat_ipk->media_dev);
> +}
> +
> +static int plat_ipk_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct v4l2_device *v4l2_dev;
> +	struct plat_ipk_dev *plat_ipk;
> +	int ret;
> +
> +	dev_dbg(dev, "Installing DW MIPI CSI-2 IPK Platform module\n");
> +
> +	plat_ipk = devm_kzalloc(dev, sizeof(*plat_ipk), GFP_KERNEL);
> +	if (!plat_ipk)
> +		return -ENOMEM;
> +
> +	spin_lock_init(&plat_ipk->slock);
> +	INIT_LIST_HEAD(&plat_ipk->pipelines);
> +	plat_ipk->pdev = pdev;
> +
> +	strlcpy(plat_ipk->media_dev.model, "SNPS IPK Platform",
> +		sizeof(plat_ipk->media_dev.model));
> +	plat_ipk->media_dev.ops = &plat_ipk_media_ops;
> +	plat_ipk->media_dev.dev = dev;
> +
> +	v4l2_dev = &plat_ipk->v4l2_dev;
> +	v4l2_dev->mdev = &plat_ipk->media_dev;
> +	strlcpy(v4l2_dev->name, "plat-ipk", sizeof(v4l2_dev->name));
> +
> +	media_device_init(&plat_ipk->media_dev);
> +
> +	ret = v4l2_device_register(dev, &plat_ipk->v4l2_dev);
> +	if (ret < 0) {
> +		v4l2_err(v4l2_dev, "Failed to register v4l2_device: %d\n", ret);
> +		return ret;
> +	}
> +
> +	platform_set_drvdata(pdev, plat_ipk);
> +
> +	ret = plat_ipk_register_platform_entities(plat_ipk, dev->of_node);
> +	if (ret)
> +		goto err_m_ent;
> +
> +	ret = plat_ipk_register_sensor_entities(plat_ipk);
> +	if (ret)
> +		goto err_m_ent;
> +
> +	if (plat_ipk->num_sensors > 0) {
> +		plat_ipk->subdev_notifier.subdevs = plat_ipk->async_subdevs;
> +		plat_ipk->subdev_notifier.num_subdevs = plat_ipk->num_sensors;
> +		plat_ipk->subdev_notifier.bound = subdev_notifier_bound;
> +		plat_ipk->subdev_notifier.complete = subdev_notifier_complete;
> +		plat_ipk->num_sensors = 0;
> +
> +		ret = v4l2_async_notifier_register(&plat_ipk->v4l2_dev,
> +						   &plat_ipk->subdev_notifier);
> +		if (ret)
> +			goto err_m_ent;
> +	}
> +
> +	return 0;
> +
> +err_m_ent:
> +	plat_ipk_unregister_entities(plat_ipk);
> +	media_device_unregister(&plat_ipk->media_dev);
> +	media_device_cleanup(&plat_ipk->media_dev);
> +	v4l2_device_unregister(&plat_ipk->v4l2_dev);
> +	return ret;
> +}
> +
> +static int plat_ipk_remove(struct platform_device *pdev)
> +{
> +	struct plat_ipk_dev *dev = platform_get_drvdata(pdev);
> +
> +	v4l2_async_notifier_unregister(&dev->subdev_notifier);
> +
> +	v4l2_device_unregister(&dev->v4l2_dev);
> +	plat_ipk_unregister_entities(dev);
> +	plat_ipk_pipelines_free(dev);
> +	media_device_unregister(&dev->media_dev);
> +	media_device_cleanup(&dev->media_dev);
> +
> +	dev_info(&pdev->dev, "Driver removed\n");
> +
> +	return 0;
> +}
> +
> +/**
> + * @short of_device_id structure
> + */
> +static const struct of_device_id plat_ipk_of_match[] = {
> +	{.compatible = "snps,plat-ipk"},
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(of, plat_ipk_of_match);
> +
> +/**
> + * @short Platform driver structure
> + */
> +static struct platform_driver plat_ipk_pdrv = {
> +	.remove = plat_ipk_remove,
> +	.probe = plat_ipk_probe,
> +	.driver = {
> +		   .name = "snps,plat-ipk",
> +		   .owner = THIS_MODULE,
> +		   .of_match_table = plat_ipk_of_match,
> +		   },
> +};
> +
> +static int __init
> +plat_ipk_init(void)
> +{
> +	request_module("dw-mipi-csi");
> +
> +	return platform_driver_register(&plat_ipk_pdrv);
> +}
> +
> +static void __exit
> +plat_ipk_exit(void)
> +{
> +	platform_driver_unregister(&plat_ipk_pdrv);
> +}
> +
> +module_init(plat_ipk_init);
> +module_exit(plat_ipk_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Ramiro Oliveira <roliveir-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>");
> +MODULE_DESCRIPTION("Platform driver for MIPI CSI-2 Host IPK");
> diff --git a/drivers/media/platform/dwc/plat_ipk.h b/drivers/media/platform/dwc/plat_ipk.h
> new file mode 100644
> index 0000000..ff2f98b
> --- /dev/null
> +++ b/drivers/media/platform/dwc/plat_ipk.h
> @@ -0,0 +1,101 @@
> +/*
> + * Copyright (C) 2016 Synopsys, Inc. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef PLAT_IPK_H_
> +#define PLAT_IPK_H_
> +
> +#include <linux/errno.h>
> +#include <linux/kernel.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +#include <media/media-entity.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-of.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "dw_mipi_csi.h"
> +#include "plat_ipk_video.h"
> +#include "video_device.h"
> +
> +#define VIDEODEV_OF_NODE_NAME	"video-device"
> +#define CSI_OF_NODE_NAME	"csi2"
> +
> +enum plat_ipk_subdev_index {
> +	IDX_SENSOR,
> +	IDX_CSI,
> +	IDX_VDEV,
> +	IDX_MAX,
> +};
> +
> +struct plat_ipk_sensor_info {
> +	struct plat_ipk_source_info pdata;
> +	struct v4l2_async_subdev asd;
> +	struct v4l2_subdev *subdev;
> +	struct mipi_csi_dev *host;
> +};
> +
> +struct plat_ipk_pipeline {
> +	struct plat_ipk_media_pipeline ep;
> +	struct list_head list;
> +	struct media_entity *vdev_entity;
> +	struct v4l2_subdev *subdevs[IDX_MAX];
> +};
> +
> +#define to_plat_ipk_pipeline(_ep)\
> +	 container_of(_ep, struct plat_ipk_pipeline, ep)
> +
> +struct mipi_csi_info {
> +	struct v4l2_subdev *sd;
> +	int id;
> +};
> +
> +/**
> + * @short Structure to embed device driver information
> + */
> +struct plat_ipk_dev {
> +	struct mipi_csi_info		mipi_csi[CSI_MAX_ENTITIES];
> +	struct video_device_dev		*vid_dev;
> +	struct device			*dev;
> +	struct media_device		media_dev;
> +	struct v4l2_device		v4l2_dev;
> +	struct platform_device		*pdev;
> +	struct plat_ipk_sensor_info	sensor[PLAT_MAX_SENSORS];
> +	struct v4l2_async_notifier	subdev_notifier;
> +	struct v4l2_async_subdev	*async_subdevs[PLAT_MAX_SENSORS];
> +	spinlock_t			slock;
> +	struct list_head		pipelines;
> +	int				num_sensors;
> +	struct media_entity_graph	link_setup_graph;
> +};
> +
> +static inline struct plat_ipk_dev *
> +entity_to_plat_ipk_mdev(struct media_entity *me)
> +{
> +	return me->graph_obj.mdev == NULL ? NULL :
> +	    container_of(me->graph_obj.mdev, struct plat_ipk_dev, media_dev);
> +}
> +
> +static inline struct plat_ipk_dev *
> +notifier_to_plat_ipk(struct v4l2_async_notifier *n)
> +{
> +	return container_of(n, struct plat_ipk_dev, subdev_notifier);
> +}
> +
> +static inline void
> +plat_ipk_graph_unlock(struct plat_ipk_video_entity *ve)
> +{
> +	mutex_unlock(&ve->vdev.entity.graph_obj.mdev->graph_mutex);
> +}
> +
> +#endif				/* PLAT_IPK_H_ */
> diff --git a/drivers/media/platform/dwc/plat_ipk_video.h b/drivers/media/platform/dwc/plat_ipk_video.h
> new file mode 100644
> index 0000000..9956ead
> --- /dev/null
> +++ b/drivers/media/platform/dwc/plat_ipk_video.h
> @@ -0,0 +1,97 @@
> +/*
> + * Copyright (C) 2016 Synopsys, Inc. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef PLAT_IPK_INCLUDES_H_
> +#define PLAT_IPK_INCLUDES_H_
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-mediabus.h>
> +#include <media/v4l2-subdev.h>
> +
> +/*
> + * The subdevices' group IDs.
> + */
> +
> +#define MAX_WIDTH	3280
> +#define MAX_HEIGHT	1852
> +
> +#define MIN_WIDTH	640
> +#define MIN_HEIGHT	480
> +
> +#define GRP_ID_SENSOR		(10)
> +#define GRP_ID_CSI		(20)
> +#define GRP_ID_VIDEODEV		(30)
> +
> +#define CSI_MAX_ENTITIES	1
> +#define PLAT_MAX_SENSORS	1
> +
> +enum video_dev_pads {
> +	VIDEO_DEV_SD_PAD_SINK_CSI = 0,
> +	VIDEO_DEV_SD_PAD_SOURCE_DMA = 1,
> +	VIDEO_DEV_SD_PADS_NUM = 2,
> +};
> +
> +enum mipi_csi_pads {
> +	CSI_PAD_SINK = 0,
> +	CSI_PAD_SOURCE = 1,
> +	CSI_PADS_NUM = 2,
> +};
> +
> +struct plat_ipk_source_info {
> +	u16 flags;
> +	u16 mux_id;
> +};
> +
> +struct plat_ipk_fmt {
> +	char *name;
> +	u32 mbus_code;
> +	u32 fourcc;
> +	u8 depth;
> +};
> +
> +struct plat_ipk_media_pipeline;
> +
> +/*
> + * Media pipeline operations to be called from within a video node,  i.e. the
> + * last entity within the pipeline. Implemented by related media device driver.
> + */
> +struct plat_ipk_media_pipeline_ops {
> +	int (*prepare)(struct plat_ipk_media_pipeline *p,
> +			struct media_entity *me);
> +	int (*unprepare)(struct plat_ipk_media_pipeline *p);
> +	int (*open)(struct plat_ipk_media_pipeline *p,
> +			struct media_entity *me, bool resume);
> +	int (*close)(struct plat_ipk_media_pipeline *p);
> +	int (*set_stream)(struct plat_ipk_media_pipeline *p, bool state);
> +	int (*set_format)(struct plat_ipk_media_pipeline *p,
> +			struct v4l2_subdev_format *fmt);
> +};
> +
> +struct plat_ipk_video_entity {
> +	struct video_device vdev;
> +	struct plat_ipk_media_pipeline *pipe;
> +};
> +
> +struct plat_ipk_media_pipeline {
> +	struct media_pipeline mp;
> +	const struct plat_ipk_media_pipeline_ops *ops;
> +};
> +
> +static inline struct plat_ipk_video_entity *
> +vdev_to_plat_ipk_video_entity(struct video_device *vdev)
> +{
> +	return container_of(vdev, struct plat_ipk_video_entity, vdev);
> +}
> +
> +#define plat_ipk_pipeline_call(ent, op, args...)\
> +	(!(ent) ? -ENOENT : (((ent)->pipe->ops && (ent)->pipe->ops->op) ? \
> +	(ent)->pipe->ops->op(((ent)->pipe), ##args) : -ENOIOCTLCMD))	  \
> +
> +
> +#endif				/* PLAT_IPK_INCLUDES_H_ */
> diff --git a/drivers/media/platform/dwc/video_device.c b/drivers/media/platform/dwc/video_device.c
> new file mode 100644
> index 0000000..8e7033c
> --- /dev/null
> +++ b/drivers/media/platform/dwc/video_device.c
> @@ -0,0 +1,707 @@
> +/*
> + * DWC MIPI CSI-2 Host IPK video device device driver
> + *
> + * Copyright (C) 2016 Synopsys, Inc. All rights reserved.
> + * Author: Ramiro Oliveira <ramiro.oliveira-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published
> + * by the Free Software Foundation, either version 2 of the License,
> + * or (at your option) any later version.
> + */
> +
> +#include "video_device.h"
> +
> +static const struct plat_ipk_fmt vid_dev_formats[] = {
> +	{
> +		.name = "BGR888",
> +		.fourcc = V4L2_PIX_FMT_BGR24,
> +		.depth = 24,
> +		.mbus_code = MEDIA_BUS_FMT_RGB888_2X12_LE,
> +	}, {
> +		.name = "RGB565",
> +		.fourcc = V4L2_PIX_FMT_RGB565,
> +		.depth = 16,
> +		.mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE,
> +	},
> +};
> +
> +static const struct plat_ipk_fmt *
> +vid_dev_find_format(struct v4l2_format *f, int index)
> +{
> +	const struct plat_ipk_fmt *fmt = NULL;
> +	unsigned int i;
> +
> +	if (index >= (int) ARRAY_SIZE(vid_dev_formats))
> +		return NULL;

???

What's the purpose of the index argument? I get the feeling it is
a left-over from older code.

> +
> +	for (i = 0; i < ARRAY_SIZE(vid_dev_formats); ++i) {
> +		fmt = &vid_dev_formats[i];
> +		if (fmt->fourcc == f->fmt.pix.pixelformat)
> +			return fmt;
> +	}
> +	return NULL;
> +}
> +
> +/*
> + * Video node ioctl operations
> + */
> +static int
> +vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
> +{
> +	struct video_device_dev *vid_dev = video_drvdata(file);
> +
> +	strlcpy(cap->driver, VIDEO_DEVICE_NAME, sizeof(cap->driver));
> +	strlcpy(cap->card, VIDEO_DEVICE_NAME, sizeof(cap->card));
> +	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
> +		 dev_name(&vid_dev->pdev->dev));
> +
> +	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
> +	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;

Set the device_caps in struct video_device and drop these two lines.
The core will fill those in for you.

> +	return 0;
> +}
> +
> +static int
> +vidioc_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f)
> +{
> +	const struct plat_ipk_fmt *p_fmt;
> +
> +	if (f->index >= ARRAY_SIZE(vid_dev_formats))
> +		return -EINVAL;
> +
> +	p_fmt = &vid_dev_formats[f->index];
> +
> +	strlcpy(f->description, p_fmt->name, sizeof(f->description));

Don't set the description, the core will do that for you.

> +	f->pixelformat = p_fmt->fourcc;
> +
> +	return 0;
> +}
> +
> +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
> +					struct v4l2_format *f)
> +{
> +	struct video_device_dev *dev = video_drvdata(file);
> +
> +	memcpy(&f->fmt.pix, &dev->format.fmt.pix,
> +	       sizeof(struct v4l2_pix_format));

Use f->fmt.pix = dev->format.fmt.pix;

> +
> +	return 0;
> +}
> +
> +static int
> +vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
> +{
> +	const struct plat_ipk_fmt *fmt;
> +
> +	fmt = vid_dev_find_format(f, -1);
> +	if (!fmt) {
> +		f->fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565;
> +		fmt = vid_dev_find_format(f, -1);
> +	}
> +
> +	f->fmt.pix.field = V4L2_FIELD_NONE;
> +	v4l_bound_align_image(&f->fmt.pix.width, 48, MAX_WIDTH, 2,
> +			      &f->fmt.pix.height, 32, MAX_HEIGHT, 0, 0);
> +
> +	f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3;
> +	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
> +	f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> +	return 0;
> +}
> +
> +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
> +					struct v4l2_format *f)
> +{
> +	struct video_device_dev *dev = video_drvdata(file);
> +	int ret;
> +	struct v4l2_subdev_format fmt;
> +	struct v4l2_pix_format *dev_fmt_pix = &dev->format.fmt.pix;
> +
> +	if (vb2_is_busy(&dev->vb_queue))
> +		return -EBUSY;
> +
> +	ret = vidioc_try_fmt_vid_cap(file, dev, f);
> +	if (ret)
> +		return ret;
> +
> +	dev->fmt = vid_dev_find_format(f, -1);
> +	dev_fmt_pix->pixelformat = f->fmt.pix.pixelformat;
> +	dev_fmt_pix->width = f->fmt.pix.width;
> +	dev_fmt_pix->height  = f->fmt.pix.height;
> +	dev_fmt_pix->bytesperline = dev_fmt_pix->width * (dev->fmt->depth / 8);
> +	dev_fmt_pix->sizeimage =
> +			dev_fmt_pix->height * dev_fmt_pix->bytesperline;
> +
> +	fmt.format.colorspace = V4L2_COLORSPACE_SRGB;
> +	fmt.format.code = dev->fmt->mbus_code;
> +
> +	fmt.format.width = dev_fmt_pix->width;
> +	fmt.format.height = dev_fmt_pix->height;
> +
> +	ret = plat_ipk_pipeline_call(&dev->ve, set_format, &fmt);
> +
> +	return 0;
> +}
> +
> +static int vidioc_enum_framesizes(struct file *file, void *fh,
> +		       struct v4l2_frmsizeenum *fsize)
> +{
> +	static const struct v4l2_frmsize_stepwise sizes = {
> +		48, MAX_WIDTH, 4,
> +		32, MAX_HEIGHT, 1
> +	};
> +	int i;
> +
> +	if (fsize->index)
> +		return -EINVAL;
> +	for (i = 0; i < ARRAY_SIZE(vid_dev_formats); i++)
> +		if (vid_dev_formats[i].fourcc == fsize->pixel_format)
> +			break;
> +	if (i == ARRAY_SIZE(vid_dev_formats))
> +		return -EINVAL;
> +	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +	fsize->stepwise = sizes;
> +	return 0;
> +}
> +
> +static int vidioc_enum_input(struct file *file, void *priv,
> +			struct v4l2_input *input)
> +{
> +	if (input->index != 0)
> +		return -EINVAL;
> +
> +	input->type = V4L2_INPUT_TYPE_CAMERA;
> +	input->std = V4L2_STD_ALL;	/* Not sure what should go here */

Set this to 0, or just drop the line.

> +	strcpy(input->name, "Camera");
> +	return 0;
> +}
> +
> +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
> +{
> +	*i = 0;
> +	return 0;
> +}
> +
> +static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
> +{
> +	if (i != 0)
> +		return -EINVAL;
> +	return 0;
> +}
> +
> +static int
> +vid_dev_streamon(struct file *file, void *priv, enum v4l2_buf_type type)
> +{
> +	struct video_device_dev *vid_dev = video_drvdata(file);
> +	struct media_entity *entity = &vid_dev->ve.vdev.entity;
> +	int ret;
> +
> +	ret = media_entity_pipeline_start(entity, &vid_dev->ve.pipe->mp);
> +	if (ret < 0)
> +		return ret;
> +
> +	vb2_ioctl_streamon(file, priv, type);
> +	if (!ret)
> +		return ret;
> +
> +	media_entity_pipeline_stop(entity);
> +	return 0;
> +}
> +
> +static int
> +vid_dev_streamoff(struct file *file, void *priv, enum v4l2_buf_type type)
> +{
> +	struct video_device_dev *vid_dev = video_drvdata(file);
> +	int ret;
> +
> +	ret = vb2_ioctl_streamoff(file, priv, type);
> +	if (ret < 0)
> +		return ret;
> +
> +	media_entity_pipeline_stop(&vid_dev->ve.vdev.entity);
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops vid_dev_ioctl_ops = {
> +	.vidioc_querycap = vidioc_querycap,
> +	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
> +	.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
> +	.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
> +	.vidioc_enum_framesizes = vidioc_enum_framesizes,
> +	.vidioc_enum_input = vidioc_enum_input,
> +	.vidioc_g_input = vidioc_g_input,
> +	.vidioc_s_input = vidioc_s_input,
> +
> +	.vidioc_reqbufs = vb2_ioctl_reqbufs,
> +	.vidioc_create_bufs = vb2_ioctl_create_bufs,
> +	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> +	.vidioc_querybuf = vb2_ioctl_querybuf,
> +	.vidioc_qbuf = vb2_ioctl_qbuf,
> +	.vidioc_dqbuf = vb2_ioctl_dqbuf,
> +	.vidioc_streamon = vid_dev_streamon,
> +	.vidioc_streamoff = vid_dev_streamoff,
> +};
> +
> +static int
> +vid_dev_open(struct file *file)
> +{
> +	struct video_device_dev *vid_dev = video_drvdata(file);
> +	struct media_entity *me = &vid_dev->ve.vdev.entity;
> +	int ret;
> +
> +	mutex_lock(&vid_dev->lock);
> +
> +	ret = v4l2_fh_open(file);
> +	if (ret < 0)
> +		goto unlock;
> +
> +	if (!v4l2_fh_is_singular_file(file))
> +		goto unlock;
> +
> +	mutex_lock(&me->graph_obj.mdev->graph_mutex);
> +
> +	ret = plat_ipk_pipeline_call(&vid_dev->ve, open, me, true);
> +	if (ret == 0)
> +		me->use_count++;
> +
> +	mutex_unlock(&me->graph_obj.mdev->graph_mutex);
> +
> +	if (!ret)
> +		goto unlock;
> +
> +	v4l2_fh_release(file);
> +unlock:
> +	mutex_unlock(&vid_dev->lock);
> +	return ret;
> +}
> +
> +static int
> +vid_dev_release(struct file *file)
> +{
> +	struct video_device_dev *vid_dev = video_drvdata(file);
> +	struct media_entity *entity = &vid_dev->ve.vdev.entity;
> +
> +	mutex_lock(&vid_dev->lock);
> +
> +	if (v4l2_fh_is_singular_file(file)) {
> +		plat_ipk_pipeline_call(&vid_dev->ve, close);
> +		mutex_lock(&entity->graph_obj.mdev->graph_mutex);
> +		entity->use_count--;
> +		mutex_unlock(&entity->graph_obj.mdev->graph_mutex);
> +	}
> +
> +	_vb2_fop_release(file, NULL);
> +
> +	mutex_unlock(&vid_dev->lock);
> +	return 0;
> +}
> +
> +static const struct v4l2_file_operations vid_dev_fops = {
> +	.owner = THIS_MODULE,
> +	.open = vid_dev_open,
> +	.release = vid_dev_release,
> +	.write = vb2_fop_write,
> +	.read = vb2_fop_read,
> +	.poll = vb2_fop_poll,
> +	.unlocked_ioctl = video_ioctl2,
> +	.mmap = vb2_fop_mmap,
> +};
> +
> +/*
> + * VideoBuffer2 operations
> + */
> +
> +static void fill_buffer(struct video_device_dev *dev, struct rx_buffer *buf,
> +			int buf_num, unsigned long flags)
> +{
> +	int size = 0;
> +	void *vbuf = NULL;
> +
> +	if (&buf->vb == NULL)
> +		return;
> +
> +	size = vb2_plane_size(&buf->vb.vb2_buf, 0);
> +	vbuf = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
> +
> +	if (vbuf) {
> +		spin_unlock_irqrestore(&dev->slock, flags);
> +
> +		memcpy(vbuf, dev->dma_buf[buf_num].cpu_addr, size);
> +
> +		spin_lock_irqsave(&dev->slock, flags);
> +
> +		buf->vb.field = dev->format.fmt.pix.field;
> +		buf->vb.sequence++;
> +		buf->vb.vb2_buf.timestamp = ktime_get_ns();
> +	}
> +	vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +}
> +
> +static void buffer_copy_process(void *param)
> +{
> +	struct video_device_dev *dev = (struct video_device_dev *) param;
> +	unsigned long flags;
> +	struct dmaqueue *dma_q = &dev->vidq;
> +	struct rx_buffer *buf = NULL;
> +
> +	spin_lock_irqsave(&dev->slock, flags);
> +
> +	if (!list_empty(&dma_q->active)) {
> +		buf = list_entry(dma_q->active.next, struct rx_buffer, list);
> +		list_del(&buf->list);
> +		fill_buffer(dev, buf, dev->last_idx, flags);
> +	}
> +
> +	spin_unlock_irqrestore(&dev->slock, flags);
> +}
> +
> +static inline struct rx_buffer *to_rx_buffer(struct vb2_v4l2_buffer *vb2)
> +{
> +	return container_of(vb2, struct rx_buffer, vb);
> +}
> +
> +static int queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
> +			unsigned int *nplanes, unsigned int sizes[],
> +			struct device *alloc_devs[])
> +{
> +	struct video_device_dev *dev = vb2_get_drv_priv(vq);
> +	unsigned long size = 0;
> +	int i;
> +
> +	size = dev->format.fmt.pix.sizeimage;
> +	if (size == 0)
> +		return -EINVAL;
> +
> +	*nbuffers = N_BUFFERS;
> +
> +	for (i = 0; i < N_BUFFERS; i++) {
> +		dev->dma_buf[i].cpu_addr = dma_alloc_coherent(&dev->pdev->dev,
> +						dev->format.fmt.pix.sizeimage,
> +						&dev->dma_buf[i].dma_addr,
> +						GFP_KERNEL);
> +	}
> +
> +	*nplanes = 1;
> +	sizes[0] = size;
> +
> +	return 0;
> +}
> +
> +static int buffer_prepare(struct vb2_buffer *vb)
> +{
> +	struct rx_buffer *buf = NULL;
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +	int size = 0;
> +
> +	if (vb == NULL) {
> +		pr_warn("%s:vb2_buffer is null\n", FUNC_NAME);
> +		return 0;
> +	}
> +
> +	buf = to_rx_buffer(vbuf);
> +
> +	size = vb2_plane_size(&buf->vb.vb2_buf, 0);
> +	vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
> +
> +	INIT_LIST_HEAD(&buf->list);
> +	return 0;
> +}
> +
> +static void buffer_queue(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +	struct video_device_dev *dev = NULL;
> +	struct rx_buffer *buf = NULL;
> +	struct dmaqueue *vidq = NULL;
> +	struct dma_async_tx_descriptor *desc;
> +	u32 flags;
> +
> +	if (vb == NULL) {
> +		pr_warn("%s:vb2_buffer is null\n", FUNC_NAME);
> +		return;
> +	}
> +
> +	dev = vb2_get_drv_priv(vb->vb2_queue);
> +	buf = to_rx_buffer(vbuf);
> +	vidq = &dev->vidq;
> +
> +	flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK;
> +	dev->xt.dir = DMA_DEV_TO_MEM;
> +	dev->xt.src_sgl = false;
> +	dev->xt.dst_inc = false;
> +	dev->xt.dst_sgl = true;
> +	dev->xt.dst_start = dev->dma_buf[dev->idx].dma_addr;
> +
> +	dev->last_idx = dev->idx;
> +	dev->idx++;
> +	if (dev->idx >= N_BUFFERS)
> +		dev->idx = 0;
> +
> +	dev->xt.frame_size = 1;
> +	dev->sgl[0].size = dev->format.fmt.pix.bytesperline;
> +	dev->sgl[0].icg = 0;
> +	dev->xt.numf = dev->format.fmt.pix.height;
> +
> +	desc = dmaengine_prep_interleaved_dma(dev->dma, &dev->xt, flags);
> +	if (!desc) {
> +		pr_err("Failed to prepare DMA transfer\n");
> +		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +		return;
> +	}
> +
> +	desc->callback = buffer_copy_process;
> +	desc->callback_param = dev;
> +
> +	spin_lock(&dev->slock);
> +	list_add_tail(&buf->list, &vidq->active);
> +	spin_unlock(&dev->slock);
> +
> +	dmaengine_submit(desc);
> +
> +	if (vb2_is_streaming(&dev->vb_queue))
> +		dma_async_issue_pending(dev->dma);
> +}
> +
> +static int start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> +	struct video_device_dev *dev = vb2_get_drv_priv(vq);
> +
> +	dma_async_issue_pending(dev->dma);
> +
> +	return 0;
> +}
> +
> +static void stop_streaming(struct vb2_queue *vq)
> +{
> +	struct video_device_dev *dev = vb2_get_drv_priv(vq);
> +	struct dmaqueue *dma_q = &dev->vidq;
> +
> +	/* Stop and reset the DMA engine. */
> +	dmaengine_terminate_all(dev->dma);
> +
> +	while (!list_empty(&dma_q->active)) {
> +		struct rx_buffer *buf;
> +
> +		buf = list_entry(dma_q->active.next, struct rx_buffer, list);
> +		if (buf) {
> +			list_del(&buf->list);
> +			vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +		}
> +	}
> +	list_del_init(&dev->vidq.active);
> +}
> +
> +static const struct vb2_ops vb2_video_qops = {
> +	.queue_setup = queue_setup,
> +	.buf_prepare = buffer_prepare,
> +	.buf_queue = buffer_queue,
> +	.start_streaming = start_streaming,
> +	.stop_streaming = stop_streaming,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +};
> +
> +static int vid_dev_subdev_s_power(struct v4l2_subdev *sd, int on)
> +{
> +	return 0;
> +}

Just drop this empty function, shouldn't be needed.

> +
> +static int vid_dev_subdev_registered(struct v4l2_subdev *sd)
> +{
> +	struct video_device_dev *vid_dev = v4l2_get_subdevdata(sd);
> +	struct vb2_queue *q = &vid_dev->vb_queue;
> +	struct video_device *vfd = &vid_dev->ve.vdev;
> +	int ret;
> +
> +	memset(vfd, 0, sizeof(*vfd));
> +
> +	strlcpy(vfd->name, VIDEO_DEVICE_NAME, sizeof(vfd->name));
> +
> +	vfd->fops = &vid_dev_fops;
> +	vfd->ioctl_ops = &vid_dev_ioctl_ops;
> +	vfd->v4l2_dev = sd->v4l2_dev;
> +	vfd->minor = -1;
> +	vfd->release = video_device_release_empty;
> +	vfd->queue = q;
> +
> +	INIT_LIST_HEAD(&vid_dev->vidq.active);
> +	init_waitqueue_head(&vid_dev->vidq.wq);
> +	memset(q, 0, sizeof(*q));
> +	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +	q->io_modes = VB2_MMAP | VB2_USERPTR;

Add VB2_DMABUF and VB2_READ.

> +	q->ops = &vb2_video_qops;
> +	q->mem_ops = &vb2_vmalloc_memops;

Why is vmalloc used? Can't you use dma_contig or dma_sg and avoid having to copy
the image data? That's a really bad design given the amount of video data that
you have to copy.

> +	q->buf_struct_size = sizeof(struct rx_buffer);
> +	q->drv_priv = vid_dev;
> +	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	q->lock = &vid_dev->lock;
> +
> +	ret = vb2_queue_init(q);
> +	if (ret < 0)
> +		return ret;
> +
> +	vid_dev->vd_pad.flags = MEDIA_PAD_FL_SINK;
> +	ret = media_entity_pads_init(&vfd->entity, 1, &vid_dev->vd_pad);
> +	if (ret < 0)
> +		return ret;
> +
> +	video_set_drvdata(vfd, vid_dev);
> +	vid_dev->ve.pipe = v4l2_get_subdev_hostdata(sd);
> +
> +	ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1);
> +	if (ret < 0) {
> +		media_entity_cleanup(&vfd->entity);
> +		vid_dev->ve.pipe = NULL;
> +		return ret;
> +	}
> +
> +	v4l2_info(sd->v4l2_dev, "Registered %s as /dev/%s\n",
> +		  vfd->name, video_device_node_name(vfd));
> +	return 0;
> +}
> +
> +static void vid_dev_subdev_unregistered(struct v4l2_subdev *sd)
> +{
> +	struct video_device_dev *vid_dev = v4l2_get_subdevdata(sd);
> +
> +	if (vid_dev == NULL)
> +		return;
> +
> +	mutex_lock(&vid_dev->lock);
> +
> +	if (video_is_registered(&vid_dev->ve.vdev)) {
> +		video_unregister_device(&vid_dev->ve.vdev);
> +		media_entity_cleanup(&vid_dev->ve.vdev.entity);
> +		vid_dev->ve.pipe = NULL;
> +	}
> +
> +	mutex_unlock(&vid_dev->lock);
> +}
> +
> +static const struct v4l2_subdev_internal_ops vid_dev_subdev_internal_ops = {
> +	.registered = vid_dev_subdev_registered,
> +	.unregistered = vid_dev_subdev_unregistered,
> +};
> +
> +static const struct v4l2_subdev_core_ops vid_dev_subdev_core_ops = {
> +	.s_power = vid_dev_subdev_s_power,
> +};
> +
> +static struct v4l2_subdev_ops vid_dev_subdev_ops = {
> +	.core = &vid_dev_subdev_core_ops,
> +};
> +
> +static int vid_dev_create_capture_subdev(struct video_device_dev *vid_dev)
> +{
> +	struct v4l2_subdev *sd = &vid_dev->subdev;
> +	int ret;
> +
> +	v4l2_subdev_init(sd, &vid_dev_subdev_ops);
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	snprintf(sd->name, sizeof(sd->name), "Capture device");
> +
> +	vid_dev->subdev_pads[VIDEO_DEV_SD_PAD_SINK_CSI].flags =
> +		MEDIA_PAD_FL_SOURCE;
> +	vid_dev->subdev_pads[VIDEO_DEV_SD_PAD_SOURCE_DMA].flags =
> +		MEDIA_PAD_FL_SINK;
> +	ret = media_entity_pads_init(&sd->entity, VIDEO_DEV_SD_PADS_NUM,
> +				   vid_dev->subdev_pads);
> +	if (ret)
> +		return ret;
> +
> +	sd->internal_ops = &vid_dev_subdev_internal_ops;
> +	sd->owner = THIS_MODULE;
> +	v4l2_set_subdevdata(sd, vid_dev);
> +
> +	return 0;
> +}
> +
> +static void vid_dev_unregister_subdev(struct video_device_dev *vid_dev)
> +{
> +	struct v4l2_subdev *sd = &vid_dev->subdev;
> +
> +	v4l2_device_unregister_subdev(sd);
> +	media_entity_cleanup(&sd->entity);
> +	v4l2_set_subdevdata(sd, NULL);
> +}
> +
> +static const struct of_device_id vid_dev_of_match[];
> +
> +static int vid_dev_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	const struct of_device_id *of_id;
> +	int ret = 0;
> +	struct video_device_dev *vid_dev;
> +
> +	dev_dbg(dev, "Installing IPK Video Device module\n");
> +
> +	if (!dev->of_node)
> +		return -ENODEV;
> +
> +	vid_dev = devm_kzalloc(dev, sizeof(*vid_dev), GFP_KERNEL);
> +	if (!vid_dev)
> +		return -ENOMEM;
> +
> +	of_id = of_match_node(vid_dev_of_match, dev->of_node);
> +	if (WARN_ON(of_id == NULL))
> +		return -EINVAL;
> +
> +	vid_dev->pdev = pdev;
> +
> +	spin_lock_init(&vid_dev->slock);
> +	mutex_init(&vid_dev->lock);
> +
> +	dev_dbg(&pdev->dev, "Requesting DMA\n");
> +	vid_dev->dma = dma_request_slave_channel(&pdev->dev, "vdma0");
> +	if (vid_dev->dma == NULL) {
> +		dev_err(&pdev->dev, "no VDMA channel found\n");
> +		ret = -ENODEV;
> +		goto end;
> +	}
> +
> +	ret = vid_dev_create_capture_subdev(vid_dev);
> +	if (ret)
> +		goto end;
> +
> +	platform_set_drvdata(pdev, vid_dev);
> +
> +	dev_info(dev, "Video Device registered successfully\n");
> +	return 0;
> +end:
> +	dev_err(dev, "Video Device not registered!!\n");
> +	return ret;
> +}
> +
> +static int vid_dev_remove(struct platform_device *pdev)
> +{
> +	struct video_device_dev *dev = platform_get_drvdata(pdev);
> +
> +	vid_dev_unregister_subdev(dev);
> +	dev_info(&pdev->dev, "Driver removed\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id vid_dev_of_match[] = {
> +	{.compatible = "snps,video-device"},
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(of, vid_dev_of_match);
> +
> +static struct platform_driver __refdata vid_dev_pdrv = {
> +	.remove = vid_dev_remove,
> +	.probe = vid_dev_probe,
> +	.driver = {
> +		   .name = VIDEO_DEVICE_NAME,
> +		   .owner = THIS_MODULE,
> +		   .of_match_table = vid_dev_of_match,
> +		   },
> +};
> +
> +module_platform_driver(vid_dev_pdrv);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Ramiro Oliveira <roliveir-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>");
> +MODULE_DESCRIPTION("Driver for configuring DMA and Video Device");
> diff --git a/drivers/media/platform/dwc/video_device.h b/drivers/media/platform/dwc/video_device.h
> new file mode 100644
> index 0000000..2d8a1a0
> --- /dev/null
> +++ b/drivers/media/platform/dwc/video_device.h
> @@ -0,0 +1,85 @@
> +/*
> + * Copyright (C) 2016 Synopsys, Inc. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef VIDEO_DEVICE_H_
> +#define VIDEO_DEVICE_H_
> +
> +#include <linux/delay.h>
> +#include <linux/dma/xilinx_dma.h>
> +#include <linux/errno.h>
> +#include <linux/io.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +#include <linux/wait.h>
> +#include <media/media-entity.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fh.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-vmalloc.h>
> +
> +
> +
> +#include "plat_ipk_video.h"
> +
> +#define N_BUFFERS 3
> +
> +#define VIDEO_DEVICE_NAME	"video-device"
> +
> +#define FUNC_NAME __func__
> +
> +struct rx_buffer {
> +	/** @short Buffer for video frames */
> +	struct vb2_v4l2_buffer vb;
> +	struct list_head list;
> +
> +	dma_addr_t dma_addr;
> +	void *cpu_addr;
> +};
> +
> +struct dmaqueue {
> +	struct list_head active;
> +	wait_queue_head_t wq;
> +};
> +
> +/**
> + * @short Structure to embed device driver information
> + */
> +struct video_device_dev {
> +	struct platform_device *pdev;
> +	struct v4l2_device *v4l2_dev;
> +	struct v4l2_subdev subdev;
> +	struct media_pad vd_pad;
> +	struct media_pad subdev_pads[VIDEO_DEV_SD_PADS_NUM];
> +	struct mutex lock;
> +	spinlock_t slock;
> +	struct plat_ipk_video_entity ve;
> +	struct v4l2_format format;
> +	struct v4l2_pix_format pix_format;
> +	const struct plat_ipk_fmt *fmt;
> +	unsigned long *alloc_ctx;
> +
> +	/* Buffer and DMA */
> +	struct vb2_queue vb_queue;
> +	int idx;
> +	int last_idx;
> +	struct dmaqueue vidq;
> +	struct rx_buffer dma_buf[N_BUFFERS];
> +	struct dma_chan *dma;
> +	struct dma_interleaved_template xt;
> +	struct data_chunk sgl[1];
> +};
> +
> +#endif				/* VIDEO_DEVICE_H_ */
>

Regards,

	Hans
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH 1/3] ARM: at91: flush the L2 cache before entering cpu idle
From: Russell King - ARM Linux @ 2017-01-11 11:18 UTC (permalink / raw)
  To: Jean-Jacques Hiblot
  Cc: Wenyou Yang, Alexandre Belloni, Mark Rutland, devicetree,
	Nicolas Ferre, Linux Kernel Mailing List, robh+dt,
	linux-arm-kernel@lists.infradead.org
In-Reply-To: <CACh+v5M3tU4WHMqAX0zm2H=zzwc0+XChyG1tnH2aHxaacm6C=A@mail.gmail.com>

On Wed, Jan 11, 2017 at 12:05:05PM +0100, Jean-Jacques Hiblot wrote:
> 2017-01-11 9:15 GMT+01:00  <Wenyou.Yang@microchip.com>:
> > Hi Jean-Jacques,
> >
> >> -----Original Message-----
> >> From: Jean-Jacques Hiblot [mailto:jjhiblot@gmail.com]
> >> Sent: 2017年1月11日 0:51
> >> To: Alexandre Belloni <alexandre.belloni@free-electrons.com>
> >> Cc: Wenyou Yang - A41535 <Wenyou.Yang@microchip.com>; Mark Rutland
> >> <mark.rutland@arm.com>; devicetree <devicetree@vger.kernel.org>; Russell
> >> King <linux@arm.linux.org.uk>; Wenyou Yang - A41535
> >> <Wenyou.Yang@microchip.com>; Nicolas Ferre <nicolas.ferre@atmel.com>;
> >> Linux Kernel Mailing List <linux-kernel@vger.kernel.org>; Rob Herring
> >> <robh+dt@kernel.org>; linux-arm-kernel@lists.infradead.org
> >> Subject: Re: [PATCH 1/3] ARM: at91: flush the L2 cache before entering cpu idle
> >>
> >> 2017-01-10 17:18 GMT+01:00 Alexandre Belloni
> >> <alexandre.belloni@free-electrons.com>:
> >> > I though a bit more about it, and I don't really like the new
> >> > compatible string. I don't feel this should be necessary.
> >> >
> >> > What about the following:
> >> >
> >> > diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c index
> >> > b4332b727e9c..0333aca63e44 100644
> >> > --- a/arch/arm/mach-at91/pm.c
> >> > +++ b/arch/arm/mach-at91/pm.c
> >> > @@ -53,6 +53,7 @@ extern void at91_pinctrl_gpio_resume(void);  static
> >> > struct {
> >> >         unsigned long uhp_udp_mask;
> >> >         int memctrl;
> >> > +       bool has_l2_cache;
> >> >  } at91_pm_data;
> >> >
> >> >  void __iomem *at91_ramc_base[2];
> >> > @@ -267,6 +268,11 @@ static void at91_ddr_standby(void)
> >> >         u32 lpr0, lpr1 = 0;
> >> >         u32 saved_lpr0, saved_lpr1 = 0;
> >> >
> >>
> >> > +       if (at91_pm_data.has_l2_cache) {
> >> > +               flush_cache_all();
> >> what is the point of calling flush_cache_all() here ? Do we really care that dirty
> >> data in L1 is written to DDR ? I may be missing something but to me it's just extra
> >> latency.
> >
> > Are you mean use outer_flush_all() to flush all cache lines in the outer cache only?
> 
> Yes that's what I meant. You see, you don't flush the cache for
> sama5d3 so it shouldn't be required either for sam5d4. You should be
> able to test it quickly and see if L1 flush is indeed required by
> replacing  flush_cache_all() with outer_flush_all(). BTW is highly
> probable that L2 cache flush is done in outer_disable() so calling
> outer_flush_all() is probably no required.

Please don't.  Read the comments in the code, and understand the APIs
that you're suggesting people use _before_ making the suggestion:

/**
 * outer_flush_all - clean and invalidate all cache lines in the outer cache
 *
 * Note: depending on implementation, this may not be atomic - it must
 * only be called with interrupts disabled and no other active outer
 * cache masters.
 *
 * It is intended that this function is only used by implementations
 * needing to override the outer_cache.disable() method due to security.
 * (Some implementations perform this as a clean followed by an invalidate.)
 */

So, outer_flush_all() should not be called except from L2 cache code
implementing the outer_disable() function - it's not intended for
platforms to use.

There are, however, sadly three users of outer_flush_all() which have
crept in through arm-soc, that should be outer_disable() instead.

-- 
RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.

^ permalink raw reply

* Re: [PATCH 1/3] ARM: at91: flush the L2 cache before entering cpu idle
From: Jean-Jacques Hiblot @ 2017-01-11 11:05 UTC (permalink / raw)
  To: Wenyou Yang
  Cc: Alexandre Belloni, Mark Rutland, devicetree,
	Russell King - ARM Linux, Nicolas Ferre,
	Linux Kernel Mailing List, robh+dt,
	linux-arm-kernel@lists.infradead.org
In-Reply-To: <F9F4555C4E01D7469D37975B62D0EFBB496BE4@CHN-SV-EXMX07.mchp-main.com>

2017-01-11 9:15 GMT+01:00  <Wenyou.Yang@microchip.com>:
> Hi Jean-Jacques,
>
>> -----Original Message-----
>> From: Jean-Jacques Hiblot [mailto:jjhiblot@gmail.com]
>> Sent: 2017年1月11日 0:51
>> To: Alexandre Belloni <alexandre.belloni@free-electrons.com>
>> Cc: Wenyou Yang - A41535 <Wenyou.Yang@microchip.com>; Mark Rutland
>> <mark.rutland@arm.com>; devicetree <devicetree@vger.kernel.org>; Russell
>> King <linux@arm.linux.org.uk>; Wenyou Yang - A41535
>> <Wenyou.Yang@microchip.com>; Nicolas Ferre <nicolas.ferre@atmel.com>;
>> Linux Kernel Mailing List <linux-kernel@vger.kernel.org>; Rob Herring
>> <robh+dt@kernel.org>; linux-arm-kernel@lists.infradead.org
>> Subject: Re: [PATCH 1/3] ARM: at91: flush the L2 cache before entering cpu idle
>>
>> 2017-01-10 17:18 GMT+01:00 Alexandre Belloni
>> <alexandre.belloni@free-electrons.com>:
>> > I though a bit more about it, and I don't really like the new
>> > compatible string. I don't feel this should be necessary.
>> >
>> > What about the following:
>> >
>> > diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c index
>> > b4332b727e9c..0333aca63e44 100644
>> > --- a/arch/arm/mach-at91/pm.c
>> > +++ b/arch/arm/mach-at91/pm.c
>> > @@ -53,6 +53,7 @@ extern void at91_pinctrl_gpio_resume(void);  static
>> > struct {
>> >         unsigned long uhp_udp_mask;
>> >         int memctrl;
>> > +       bool has_l2_cache;
>> >  } at91_pm_data;
>> >
>> >  void __iomem *at91_ramc_base[2];
>> > @@ -267,6 +268,11 @@ static void at91_ddr_standby(void)
>> >         u32 lpr0, lpr1 = 0;
>> >         u32 saved_lpr0, saved_lpr1 = 0;
>> >
>>
>> > +       if (at91_pm_data.has_l2_cache) {
>> > +               flush_cache_all();
>> what is the point of calling flush_cache_all() here ? Do we really care that dirty
>> data in L1 is written to DDR ? I may be missing something but to me it's just extra
>> latency.
>
> Are you mean use outer_flush_all() to flush all cache lines in the outer cache only?

Yes that's what I meant. You see, you don't flush the cache for
sama5d3 so it shouldn't be required either for sam5d4. You should be
able to test it quickly and see if L1 flush is indeed required by
replacing  flush_cache_all() with outer_flush_all(). BTW is highly
probable that L2 cache flush is done in outer_disable() so calling
outer_flush_all() is probably no required.

However the more I think on it, the more I wonder about the reason why
L2 flushing is required or to put it differently: is flusing the L2
cache the correct thing to do or just a workaround ?
Could it be that L2 is doing some maintenance operation when DDR
enters self refresh? In that case maybe a simple cache sync could be
used.

>
>> > +               outer_disable();
>> It seems to me that if there's no L2 cache, then outer_disable()  is a no-op. It
>> could be called unconditionally.
>> > +       }
>> > +
>> >         if (at91_ramc_base[1]) {
>> >                 saved_lpr1 = at91_ramc_read(1, AT91_DDRSDRC_LPR);
>> >                 lpr1 = saved_lpr1 & ~AT91_DDRSDRC_LPCB; @@ -287,6
>> > +293,9 @@ static void at91_ddr_standby(void)
>> >         at91_ramc_write(0, AT91_DDRSDRC_LPR, saved_lpr0);
>> >         if (at91_ramc_base[1])
>> >                 at91_ramc_write(1, AT91_DDRSDRC_LPR, saved_lpr1);
>> > +
>> > +       if (at91_pm_data.has_l2_cache)
>> > +               outer_resume();
>>
>> same remark as for outer_disable()
>>
>> Jean-Jacques
>>
>> >  }
>> >
>> >  /* We manage both DDRAM/SDRAM controllers, we need more than one
>> > value
>> >  * to
>> > @@ -353,6 +362,11 @@ static __init void at91_dt_ramc(void)
>> >                 return;
>> >         }
>> >
>> > +       np = of_find_compatible_node(NULL, NULL, "arm,pl310-cache");
>> > +       if (np)
>> > +               at91_pm_data.has_l2_cache = true;
>> > +       of_node_put(np);
>> > +
>> >         at91_pm_set_standby(standby);
>> >  }
>> >
>> >
>> > This has the following benefits:
>> >  - everybody will have the fix, regardless of whether the dtb is
>> > updated
>> >  - has_l2_cache can be used later in at91_pm_suspend instead of calling
>> >    it unconditionnaly (I'll send a patch)
>> >
>> >
>> > On 06/01/2017 at 14:59:45 +0800, Wenyou Yang wrote :
>> >> For the SoCs such as SAMA5D2 and SAMA5D4 which have L2 cache, flush
>> >> the L2 cache first before entering the cpu idle.
>> >>
>> >> Signed-off-by: Wenyou Yang <wenyou.yang@atmel.com>
>> >> ---
>> >>
>> >>  arch/arm/mach-at91/pm.c       | 19 +++++++++++++++++++
>> >>  drivers/memory/atmel-sdramc.c |  1 +
>> >>  2 files changed, 20 insertions(+)
>> >>
>> >> diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c index
>> >> b4332b727e9c..1a60dede1a01 100644
>> >> --- a/arch/arm/mach-at91/pm.c
>> >> +++ b/arch/arm/mach-at91/pm.c
>> >> @@ -289,6 +289,24 @@ static void at91_ddr_standby(void)
>> >>               at91_ramc_write(1, AT91_DDRSDRC_LPR, saved_lpr1);  }
>> >>
>> >> +static void at91_ddr_cache_standby(void) {
>> >> +     u32 saved_lpr;
>> >> +
>> >> +     flush_cache_all();
>> >> +     outer_disable();
>> >> +
>> >> +     saved_lpr = at91_ramc_read(0, AT91_DDRSDRC_LPR);
>> >> +     at91_ramc_write(0, AT91_DDRSDRC_LPR, (saved_lpr &
>> >> +                     (~AT91_DDRSDRC_LPCB)) |
>> >> + AT91_DDRSDRC_LPCB_SELF_REFRESH);
>> >> +
>> >> +     cpu_do_idle();
>> >> +
>> >> +     at91_ramc_write(0, AT91_DDRSDRC_LPR, saved_lpr);
>> >> +
>> >> +     outer_resume();
>> >> +}
>> >> +
>> >>  /* We manage both DDRAM/SDRAM controllers, we need more than one
>> value to
>> >>   * remember.
>> >>   */
>> >> @@ -324,6 +342,7 @@ static const struct of_device_id const ramc_ids[]
>> __initconst = {
>> >>       { .compatible = "atmel,at91sam9260-sdramc", .data =
>> at91sam9_sdram_standby },
>> >>       { .compatible = "atmel,at91sam9g45-ddramc", .data = at91_ddr_standby },
>> >>       { .compatible = "atmel,sama5d3-ddramc", .data =
>> >> at91_ddr_standby },
>> >> +     { .compatible = "atmel,sama5d4-ddramc", .data =
>> >> + at91_ddr_cache_standby },
>> >>       { /*sentinel*/ }
>> >>  };
>> >>
>> >> diff --git a/drivers/memory/atmel-sdramc.c
>> >> b/drivers/memory/atmel-sdramc.c index b418b39af180..7e5c5c6c1348
>> >> 100644
>> >> --- a/drivers/memory/atmel-sdramc.c
>> >> +++ b/drivers/memory/atmel-sdramc.c
>> >> @@ -48,6 +48,7 @@ static const struct of_device_id atmel_ramc_of_match[]
>> = {
>> >>       { .compatible = "atmel,at91sam9260-sdramc", .data =
>> &at91rm9200_caps, },
>> >>       { .compatible = "atmel,at91sam9g45-ddramc", .data =
>> &at91sam9g45_caps, },
>> >>       { .compatible = "atmel,sama5d3-ddramc", .data = &sama5d3_caps,
>> >> },
>> >> +     { .compatible = "atmel,sama5d4-ddramc", .data = &sama5d3_caps,
>> >> + },
>> >>       {},
>> >>  };
>> >>
>> >> --
>> >> 2.11.0
>> >>
>> >
>> > --
>> > Alexandre Belloni, Free Electrons
>> > Embedded Linux and Kernel engineering
>> > http://free-electrons.com
>> >
>> > _______________________________________________
>> > linux-arm-kernel mailing list
>> > linux-arm-kernel@lists.infradead.org
>> > http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCHv3 3/8] rtc: add STM32 RTC driver
From: Alexandre Belloni @ 2017-01-11 10:50 UTC (permalink / raw)
  To: Amelie DELAUNAY
  Cc: Alessandro Zummo, Rob Herring, Mark Rutland, Maxime Coquelin,
	Alexandre TORGUE, Russell King,
	rtc-linux-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Gabriel FERNANDEZ
In-Reply-To: <ff7a532e-a2a7-920a-29d9-e8a82d6ed915-qxv4g6HH51o@public.gmane.org>

On 11/01/2017 at 11:42:50 +0100, Amelie DELAUNAY wrote :
> 
> On 01/11/2017 11:17 AM, Alexandre Belloni wrote:
> > On 11/01/2017 at 11:07:16 +0100, Amelie DELAUNAY wrote :
> > > > This will never happen, tm is already checked multiple times (up to
> > > > three) in the core before this function can be called.
> > > > 
> > > You're right. I'll remove all rtc_valid_tm calls.
> > 
> > You can keep the one in read_time
> > 
> Even if rtc_valid_tm is called just after rtc->ops->read_time() in
> __rtc_read_time ?

Ah yes, I forgot it was added, you can remove all of them.

-- 
Alexandre Belloni, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

-- 
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
--- 
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply

* Re: [PATCH v2 5/5] ARM: dts: Add LEGO MINDSTORMS EV3 dts
From: Sekhar Nori @ 2017-01-11 10:42 UTC (permalink / raw)
  To: David Lechner
  Cc: Kevin Hilman, Rob Herring, Mark Rutland, devicetree,
	linux-arm-kernel, linux-kernel
In-Reply-To: <1483677228-2325-6-git-send-email-david@lechnology.com>

On Friday 06 January 2017 10:03 AM, David Lechner wrote:

> +	beeper {
> +		compatible = "pwm-beeper";
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&ehrpwm0b_pins>, <&amp_pins>;
> +		pwms = <&ehrpwm0 1 0 0>;
> +		enable-gpios = <&gpio 111 GPIO_ACTIVE_HIGH>;

Since the enable-gpios binding for pwm beeper is still not accepted, can
you drop the property or the node itself (if that makes more sense)?

> +&spi0 {
> +	status = "okay";
> +	pinctrl-names = "default";
> +	pinctrl-0 = <&spi0_pins>, <&spi0_cs0_pin>, <&spi0_cs3_pin>;
> +
> +	flash@0 {
> +		compatible = "n25q128a13", "jedec,spi-nor";
> +		reg = <0>;
> +		spi-max-frequency = <50000000>;
> +		ti,spi-wdelay = <8>;
> +
> +		/* Partitions are based on the official firmware from LEGO */
> +		partitions {
> +			#address-cells = <1>;
> +			#size-cells = <1>;
> +			partition@0 {
> +				label = "U-Boot";
> +				reg = <0 0x40000>;
> +			};
> +
> +			partition@40000 {
> +				label = "U-Boot Env";
> +				reg = <0x40000 0x10000>;
> +			};
> +
> +			partition@50000 {
> +				label = "Kernel";
> +				reg = <0x50000 0x200000>;
> +			};
> +
> +			partition@250000 {
> +				label = "Filesystem";
> +				reg = <0x250000 0xa50000>;
> +			};
> +
> +			partition@cb0000 {
> +				label = "Storage";
> +				reg = <0xcb0000 0x2f0000>;
> +			};
> +		};
> +	};
> +
> +	adc@3 {
> +		compatible = "ti-ads7957";

So looks like this works because of_register_spi_device() sets up
modalias of spi device from compatible string. I am fine with it, just
highlighting it here to make sure this is acceptable practice. I did not
really find any precedence for using SPI device name as compatible
property in existing DTS files.

> +		reg = <3>;
> +		spi-max-frequency = <10000000>;
> +		refin-supply = <&adc_ref>;
> +	};

Rest of the patch looks good to me.

Thanks,
Sekhar

^ permalink raw reply

* Re: [PATCHv3 3/8] rtc: add STM32 RTC driver
From: Amelie DELAUNAY @ 2017-01-11 10:42 UTC (permalink / raw)
  To: Alexandre Belloni
  Cc: Alessandro Zummo, Rob Herring, Mark Rutland, Maxime Coquelin,
	Alexandre TORGUE, Russell King,
	rtc-linux-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Gabriel FERNANDEZ
In-Reply-To: <20170111101708.ujcgo7kpyywymyzp-m++hUPXGwpdeoWH0uzbU5w@public.gmane.org>


On 01/11/2017 11:17 AM, Alexandre Belloni wrote:
> On 11/01/2017 at 11:07:16 +0100, Amelie DELAUNAY wrote :
>>> This will never happen, tm is already checked multiple times (up to
>>> three) in the core before this function can be called.
>>>
>> You're right. I'll remove all rtc_valid_tm calls.
>
> You can keep the one in read_time
>
Even if rtc_valid_tm is called just after rtc->ops->read_time() in 
__rtc_read_time ?

-- 
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
--- 
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply

* Re: [PATCH v8 3/3] arm64: dts: exynos: Add support for S6E3HA2 panel device on TM2 board
From: Andrzej Hajda @ 2017-01-11 10:28 UTC (permalink / raw)
  To: hoegeun kwon, Inki Dae, krzk, robh, thierry.reding, airlied,
	kgene
  Cc: dri-devel, linux-kernel, devicetree, linux-samsung-soc, cw00.choi,
	jh80.chung, andi.shyti, Hyungwon Hwang
In-Reply-To: <94285a63-68df-494c-872b-41c5810a3251@samsung.com>

On 11.01.2017 11:23, hoegeun kwon wrote:
>
> On 01/11/2017 06:39 PM, Andrzej Hajda wrote:
>> On 11.01.2017 09:40, Inki Dae wrote:
>>> 2017년 01월 11일 16:46에 Andrzej Hajda 이(가) 쓴 글:
>>>> On 11.01.2017 07:33, Hoegeun Kwon wrote:
>>>>> From: Hyungwon Hwang <human.hwang@samsung.com>
>>>>>
>>>>> This patch add the panel device tree node for S6E3HA2 display
>>>>> controller to TM2 dts.
>>>>>
>>>>> Signed-off-by: Hyungwon Hwang <human.hwang@samsung.com>
>>>>> Signed-off-by: Andrzej Hajda <a.hajda@samsung.com>
>>>>> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
>>>>> Signed-off-by: Hoegeun Kwon <hoegeun.kwon@samsung.com>
>>>>> Tested-by: Chanwoo Choi <cw00.choi@samsung.com>
>>>>> ---
>>>>>   arch/arm64/boot/dts/exynos/exynos5433-tm2.dts | 12 ++++++++++++
>>>>>   1 file changed, 12 insertions(+)
>>>>>
>>>>> diff --git a/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts b/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts
>>>>> index ddba2f8..6d362f9 100644
>>>>> --- a/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts
>>>>> +++ b/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts
>>>>> @@ -18,6 +18,18 @@
>>>>>   	compatible = "samsung,tm2", "samsung,exynos5433";
>>>>>   };
>>>>>   
>>>>> +&dsi {
>>>>> +	panel@0 {
>>>>> +		compatible = "samsung,s6e3ha2";
>>>>> +		reg = <0>;
>>>>> +		vdd3-supply = <&ldo27_reg>;
>>>>> +		vci-supply = <&ldo28_reg>;
>>>>> +		reset-gpios = <&gpg0 0 GPIO_ACTIVE_LOW>;
>>>>> +		enable-gpios = <&gpf1 5 GPIO_ACTIVE_HIGH>;
>>>>> +		te-gpios = <&gpf1 3 GPIO_ACTIVE_HIGH>;
>>>> The same here (as in 1st comment) , te-gpios should be dropper - decon
>>>> uses hw-trigger.
>>> Reasonable to remove te-gpios property but this change would make MIPI-DSI driver probing to be failed so MIPI-DSI driver should be fixed together.
>>>
>>> Thanks.
>> OK, I forgot it was not yet ported to mainline.
>>
>> Regards
>> Andrzej
> I received a reply while I was writing the mail.
> so, how about removing te-gpios later?

I think this is a good solution, just do not forget to change it to
optional in bindings, to make it removable.

Regards
Andrzej

^ permalink raw reply

* Re: [PATCH v8 3/3] arm64: dts: exynos: Add support for S6E3HA2 panel device on TM2 board
From: hoegeun kwon @ 2017-01-11 10:27 UTC (permalink / raw)
  To: Andrzej Hajda, krzk, robh, thierry.reding, airlied, kgene
  Cc: devicetree, linux-samsung-soc, linux-kernel, dri-devel, cw00.choi,
	jh80.chung, andi.shyti, Hyungwon Hwang, Hoegeun Kwon
In-Reply-To: <2f362dda-6c57-6aa4-5572-b27c34c4c78e@samsung.com>



On 01/11/2017 06:51 PM, hoegeun kwon wrote:
>
>
> On 01/11/2017 04:46 PM, Andrzej Hajda wrote:
>> On 11.01.2017 07:33, Hoegeun Kwon wrote:
>>> From: Hyungwon Hwang <human.hwang@samsung.com>
>>>
>>> This patch add the panel device tree node for S6E3HA2 display
>>> controller to TM2 dts.
>>>
>>> Signed-off-by: Hyungwon Hwang <human.hwang@samsung.com>
>>> Signed-off-by: Andrzej Hajda <a.hajda@samsung.com>
>>> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
>>> Signed-off-by: Hoegeun Kwon <hoegeun.kwon@samsung.com>
>>> Tested-by: Chanwoo Choi <cw00.choi@samsung.com>
>>> ---
>>>   arch/arm64/boot/dts/exynos/exynos5433-tm2.dts | 12 ++++++++++++
>>>   1 file changed, 12 insertions(+)
>>>
>>> diff --git a/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts 
>>> b/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts
>>> index ddba2f8..6d362f9 100644
>>> --- a/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts
>>> +++ b/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts
>>> @@ -18,6 +18,18 @@
>>>       compatible = "samsung,tm2", "samsung,exynos5433";
>>>   };
>>>   +&dsi {
>>> +    panel@0 {
>>> +        compatible = "samsung,s6e3ha2";
>>> +        reg = <0>;
>>> +        vdd3-supply = <&ldo27_reg>;
>>> +        vci-supply = <&ldo28_reg>;
>>> +        reset-gpios = <&gpg0 0 GPIO_ACTIVE_LOW>;
>>> +        enable-gpios = <&gpf1 5 GPIO_ACTIVE_HIGH>;
>>> +        te-gpios = <&gpf1 3 GPIO_ACTIVE_HIGH>;
>> The same here (as in 1st comment) , te-gpios should be dropper - decon
>> uses hw-trigger.
>
> Hi Andrzej,
>
> Thanks for your quick review.
>
> Reasonable to remove te-gpios property,
> The Tizen public already has [1] your patch applied and te-gpios removed.
> So I will add [1] to the V9 patch.
>
> [1] 
> https://review.tizen.org/gerrit/gitweb?p=platform/kernel/linux-exynos.git;a=commitdiff;h=468769bf6abbaaed2547b8c43e989ab5dc787900

I'm sorry URL address is wrong.
Correct address below:

[1] 
https://git.tizen.org/cgit/platform/kernel/linux-exynos/commit/?h=tizen&id=468769bf6abbaaed2547b8c43e989ab5dc787900

Regards,
Hoegeun

>
> Best Regards,
> Hoegeun
>
>>
>> Regards
>> Andrzej
>>> +    };
>>> +};
>>> +
>>>   &hsi2c_9 {
>>>       status = "okay";
>>
>>
>>
>
>
>

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

^ permalink raw reply

* Re: [PATCH v8 3/3] arm64: dts: exynos: Add support for S6E3HA2 panel device on TM2 board
From: hoegeun kwon @ 2017-01-11 10:23 UTC (permalink / raw)
  To: Andrzej Hajda, Inki Dae, krzk, robh, thierry.reding, airlied,
	kgene
  Cc: dri-devel, linux-kernel, devicetree, linux-samsung-soc, cw00.choi,
	jh80.chung, andi.shyti, Hyungwon Hwang
In-Reply-To: <581db2a8-e357-a3ba-9464-fa8f83f95c09@samsung.com>



On 01/11/2017 06:39 PM, Andrzej Hajda wrote:
> On 11.01.2017 09:40, Inki Dae wrote:
>> 2017년 01월 11일 16:46에 Andrzej Hajda 이(가) 쓴 글:
>>> On 11.01.2017 07:33, Hoegeun Kwon wrote:
>>>> From: Hyungwon Hwang <human.hwang@samsung.com>
>>>>
>>>> This patch add the panel device tree node for S6E3HA2 display
>>>> controller to TM2 dts.
>>>>
>>>> Signed-off-by: Hyungwon Hwang <human.hwang@samsung.com>
>>>> Signed-off-by: Andrzej Hajda <a.hajda@samsung.com>
>>>> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
>>>> Signed-off-by: Hoegeun Kwon <hoegeun.kwon@samsung.com>
>>>> Tested-by: Chanwoo Choi <cw00.choi@samsung.com>
>>>> ---
>>>>   arch/arm64/boot/dts/exynos/exynos5433-tm2.dts | 12 ++++++++++++
>>>>   1 file changed, 12 insertions(+)
>>>>
>>>> diff --git a/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts b/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts
>>>> index ddba2f8..6d362f9 100644
>>>> --- a/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts
>>>> +++ b/arch/arm64/boot/dts/exynos/exynos5433-tm2.dts
>>>> @@ -18,6 +18,18 @@
>>>>   	compatible = "samsung,tm2", "samsung,exynos5433";
>>>>   };
>>>>   
>>>> +&dsi {
>>>> +	panel@0 {
>>>> +		compatible = "samsung,s6e3ha2";
>>>> +		reg = <0>;
>>>> +		vdd3-supply = <&ldo27_reg>;
>>>> +		vci-supply = <&ldo28_reg>;
>>>> +		reset-gpios = <&gpg0 0 GPIO_ACTIVE_LOW>;
>>>> +		enable-gpios = <&gpf1 5 GPIO_ACTIVE_HIGH>;
>>>> +		te-gpios = <&gpf1 3 GPIO_ACTIVE_HIGH>;
>>> The same here (as in 1st comment) , te-gpios should be dropper - decon
>>> uses hw-trigger.
>> Reasonable to remove te-gpios property but this change would make MIPI-DSI driver probing to be failed so MIPI-DSI driver should be fixed together.
>>
>> Thanks.
> OK, I forgot it was not yet ported to mainline.
>
> Regards
> Andrzej

I received a reply while I was writing the mail.
so, how about removing te-gpios later?

Best Regards,
Hoegeun

>
>>> Regards
>>> Andrzej
>>>> +	};
>>>> +};
>>>> +
>>>>   &hsi2c_9 {
>>>>   	status = "okay";
>>>>   
>>>
>>>
>
>

^ permalink raw reply

* Re: [PATCHv3 3/8] rtc: add STM32 RTC driver
From: Alexandre Belloni @ 2017-01-11 10:17 UTC (permalink / raw)
  To: Amelie DELAUNAY
  Cc: Alessandro Zummo, Rob Herring, Mark Rutland, Maxime Coquelin,
	Alexandre TORGUE, Russell King,
	rtc-linux-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Gabriel FERNANDEZ
In-Reply-To: <1feb3a23-7450-2b5c-9c1c-c7ecacd7fa17-qxv4g6HH51o@public.gmane.org>

On 11/01/2017 at 11:07:16 +0100, Amelie DELAUNAY wrote :
> > This will never happen, tm is already checked multiple times (up to
> > three) in the core before this function can be called.
> > 
> You're right. I'll remove all rtc_valid_tm calls.

You can keep the one in read_time

-- 
Alexandre Belloni, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* [PATCH v2 3/3] thermal: zx2967: add thermal driver for ZTE's zx2967 family
From: Baoyou Xie @ 2017-01-11 10:14 UTC (permalink / raw)
  To: rui.zhang, edubezval, robh+dt, mark.rutland, jun.nie
  Cc: linux-pm, devicetree, linux-kernel, linux-arm-kernel, shawnguo,
	baoyou.xie, xie.baoyou, chen.chaokai, wang.qiang01
In-Reply-To: <1484129651-17531-1-git-send-email-baoyou.xie@linaro.org>

This patch adds thermal driver for ZTE's zx2967 family.

Signed-off-by: Baoyou Xie <baoyou.xie@linaro.org>
---
 drivers/thermal/Kconfig          |   6 +
 drivers/thermal/Makefile         |   1 +
 drivers/thermal/zx2967_thermal.c | 247 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 254 insertions(+)
 create mode 100644 drivers/thermal/zx2967_thermal.c

diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 18f2de6..0dd597e 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -445,3 +445,9 @@ config BCM2835_THERMAL
 	  Support for thermal sensors on Broadcom bcm2835 SoCs.
 
 endif
+
+config ZX2967_THERMAL
+	tristate "Thermal sensors on zx2967 SoC"
+	depends on ARCH_ZX
+	help
+	  Support for thermal sensors on ZTE zx2967 SoCs.
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 677c6d9..c00c05e 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -57,3 +57,4 @@ obj-$(CONFIG_HISI_THERMAL)     += hisi_thermal.o
 obj-$(CONFIG_MTK_THERMAL)	+= mtk_thermal.o
 obj-$(CONFIG_GENERIC_ADC_THERMAL)	+= thermal-generic-adc.o
 obj-$(CONFIG_BCM2835_THERMAL)	+= bcm2835_thermal.o
+obj-$(CONFIG_ZX2967_THERMAL)	+= zx2967_thermal.o
diff --git a/drivers/thermal/zx2967_thermal.c b/drivers/thermal/zx2967_thermal.c
new file mode 100644
index 0000000..bdd2d5e
--- /dev/null
+++ b/drivers/thermal/zx2967_thermal.c
@@ -0,0 +1,247 @@
+/*
+ * ZTE's zx2967 family thermal sensor driver
+ *
+ * Copyright (C) 2017 ZTE Ltd.
+ *
+ * Author: Baoyou Xie <baoyou.xie@linaro.org>
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+/* Power Mode: 0->low 1->high */
+#define ZX2967_THERMAL_POWER_MODE	(0)
+
+/* DCF Control Register */
+#define ZX2967_THERMAL_DCF		0x4
+
+/* Selection Register */
+#define ZX2967_THERMAL_SEL		0x8
+
+/* Control Register */
+#define ZX2967_THERMAL_CTRL		0x10
+
+#define ZX2967_THERMAL_READY		(0x1000)
+#define ZX2967_THERMAL_TEMP_MASK	(0xfff)
+#define ZX2967_THERMAL_ID_MASK		(0x18)
+#define ZX2967_THERMAL_ID0		(0x8)
+#define ZX2967_THERMAL_ID1		(0x10)
+
+struct zx2967_thermal_sensor {
+	struct zx2967_thermal_priv *priv;
+	struct thermal_zone_device *tzd;
+	int id;
+};
+
+#define NUM_SENSORS	1
+
+struct zx2967_thermal_priv {
+	struct zx2967_thermal_sensor	sensors[NUM_SENSORS];
+	struct mutex			lock;
+	struct clk			*clk_gate;
+	struct clk			*pclk;
+	void __iomem			*regs;
+};
+
+static int zx2967_thermal_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev);
+
+	if (priv && priv->pclk)
+		clk_disable_unprepare(priv->pclk);
+
+	if (priv && priv->clk_gate)
+		clk_disable_unprepare(priv->clk_gate);
+
+	return 0;
+}
+
+static int zx2967_thermal_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev);
+	int error;
+
+	error = clk_prepare_enable(priv->clk_gate);
+	if (error)
+		return error;
+
+	error = clk_prepare_enable(priv->pclk);
+	if (error) {
+		clk_disable_unprepare(priv->clk_gate);
+		return error;
+	}
+
+	return 0;
+}
+
+static int zx2967_thermal_get_temp(void *data, int *temp)
+{
+	void __iomem *regs;
+	struct zx2967_thermal_sensor *sensor = data;
+	struct zx2967_thermal_priv *priv = sensor->priv;
+	unsigned long timeout = jiffies + msecs_to_jiffies(100);
+	u32 val, sel_id;
+
+	regs = priv->regs;
+	mutex_lock(&priv->lock);
+
+	writel_relaxed(0, regs + ZX2967_THERMAL_POWER_MODE);
+	writel_relaxed(2, regs + ZX2967_THERMAL_DCF);
+
+	val = readl_relaxed(regs + ZX2967_THERMAL_SEL);
+	val &= ~ZX2967_THERMAL_ID_MASK;
+	sel_id = sensor->id ? ZX2967_THERMAL_ID0 : ZX2967_THERMAL_ID1;
+	val |= sel_id;
+	writel_relaxed(val, regs + ZX2967_THERMAL_SEL);
+
+	usleep_range(100, 300);
+	val = readl_relaxed(regs + ZX2967_THERMAL_CTRL);
+	while (!(val & ZX2967_THERMAL_READY)) {
+		if (time_after(jiffies, timeout)) {
+			pr_err("Thermal sensor %d data timeout\n",
+			       sensor->id);
+			mutex_unlock(&priv->lock);
+			return -ETIMEDOUT;
+		}
+		val = readl_relaxed(regs + ZX2967_THERMAL_CTRL);
+	}
+
+	writel_relaxed(3, regs + ZX2967_THERMAL_DCF);
+	val = readl_relaxed(regs + ZX2967_THERMAL_CTRL)
+			 & ZX2967_THERMAL_TEMP_MASK;
+	writel_relaxed(1, regs + ZX2967_THERMAL_POWER_MODE);
+
+	/** Calculate temperature */
+	*temp = DIV_ROUND_CLOSEST((val - 922) * 1000, 1951);
+
+	mutex_unlock(&priv->lock);
+
+	return 0;
+}
+
+static struct thermal_zone_of_device_ops zx2967_of_thermal_ops = {
+	.get_temp = zx2967_thermal_get_temp,
+};
+
+static int zx2967_thermal_probe(struct platform_device *pdev)
+{
+	struct zx2967_thermal_priv *priv;
+	struct resource *res;
+	int ret, i;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	priv->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(priv->regs))
+		return PTR_ERR(priv->regs);
+
+	priv->clk_gate = devm_clk_get(&pdev->dev, "gate");
+	if (IS_ERR(priv->clk_gate)) {
+		ret = PTR_ERR(priv->clk_gate);
+		dev_err(&pdev->dev, "failed to get clock gate: %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(priv->clk_gate);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to enable converter clock: %d\n",
+			ret);
+		return ret;
+	}
+
+	priv->pclk = devm_clk_get(&pdev->dev, "pclk");
+	if (IS_ERR(priv->pclk)) {
+		ret = PTR_ERR(priv->pclk);
+		dev_err(&pdev->dev, "failed to get apb clock: %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(priv->pclk);
+	if (ret) {
+		clk_disable_unprepare(priv->clk_gate);
+		dev_err(&pdev->dev, "failed to enable converter clock: %d\n",
+			ret);
+		return ret;
+	}
+
+	mutex_init(&priv->lock);
+	for (i = 0; i < NUM_SENSORS; i++) {
+		struct zx2967_thermal_sensor *sensor = &priv->sensors[i];
+
+		sensor->priv = priv;
+		sensor->id = i;
+		sensor->tzd = thermal_zone_of_sensor_register(&pdev->dev,
+					i, sensor, &zx2967_of_thermal_ops);
+		if (IS_ERR(sensor->tzd)) {
+			ret = PTR_ERR(sensor->tzd);
+			dev_err(&pdev->dev, "failed to register sensor %d: %d\n",
+				i, ret);
+			goto remove_ts;
+		}
+	}
+	platform_set_drvdata(pdev, priv);
+
+	return 0;
+
+remove_ts:
+	clk_disable_unprepare(priv->clk_gate);
+	clk_disable_unprepare(priv->pclk);
+	for (i--; i >= 0; i--)
+		thermal_zone_of_sensor_unregister(&pdev->dev,
+						  priv->sensors[i].tzd);
+
+	return ret;
+}
+
+static int zx2967_thermal_exit(struct platform_device *pdev)
+{
+	struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev);
+	int i;
+
+	for (i = 0; i < NUM_SENSORS; i++) {
+		struct zx2967_thermal_sensor *sensor = &priv->sensors[i];
+
+		thermal_zone_of_sensor_unregister(&pdev->dev, sensor->tzd);
+	}
+	clk_disable_unprepare(priv->pclk);
+	clk_disable_unprepare(priv->clk_gate);
+
+	return 0;
+}
+
+static const struct of_device_id zx2967_thermal_id_table[] = {
+	{ .compatible = "zte,zx296718-thermal" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, zx2967_thermal_id_table);
+
+static SIMPLE_DEV_PM_OPS(zx2967_thermal_pm_ops,
+			 zx2967_thermal_suspend, zx2967_thermal_resume);
+
+static struct platform_driver zx2967_thermal_driver = {
+	.probe = zx2967_thermal_probe,
+	.remove = zx2967_thermal_exit,
+	.driver = {
+		.name = "zx2967_thermal",
+		.of_match_table = zx2967_thermal_id_table,
+		.pm = &zx2967_thermal_pm_ops,
+	},
+};
+module_platform_driver(zx2967_thermal_driver);
+
+MODULE_AUTHOR("Baoyou Xie <baoyou.xie@linaro.org>");
+MODULE_DESCRIPTION("ZTE zx2967 thermal driver");
+MODULE_LICENSE("GPL");
-- 
2.7.4

^ permalink raw reply related

* [PATCH v2 2/3] MAINTAINERS: add zx2967 thermal drivers to ARM ZTE architecture
From: Baoyou Xie @ 2017-01-11 10:14 UTC (permalink / raw)
  To: rui.zhang, edubezval, robh+dt, mark.rutland, jun.nie
  Cc: linux-pm, devicetree, linux-kernel, linux-arm-kernel, shawnguo,
	baoyou.xie, xie.baoyou, chen.chaokai, wang.qiang01
In-Reply-To: <1484129651-17531-1-git-send-email-baoyou.xie@linaro.org>

Add the zx2967 thermal drivers as maintained by ARM ZTE
architecture maintainers, as they're parts of the core IP.

Signed-off-by: Baoyou Xie <baoyou.xie@linaro.org>
---
 MAINTAINERS | 1 +
 1 file changed, 1 insertion(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 64f04df..2593296 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1981,6 +1981,7 @@ S:	Maintained
 F:	arch/arm/mach-zx/
 F:	drivers/clk/zte/
 F:	drivers/soc/zte/
+F:	drivers/thermal/zx*
 F:	Documentation/devicetree/bindings/arm/zte.txt
 F:	Documentation/devicetree/bindings/clock/zx296702-clk.txt
 F:	Documentation/devicetree/bindings/soc/zte/
-- 
2.7.4

^ 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