Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 3/4] arm64: arch_timer: Work around Erratum Hisilicon-161601
From: Ding Tianhong @ 2017-01-05  5:31 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483594317-10732-1-git-send-email-dingtianhong@huawei.com>

Erratum Hisilicon-161601 says that the ARM generic timer counter "has the
potential to contain an erroneous value when the timer value changes".
Accesses to TVAL (both read and write) are also affected due to the implicit counter
read.  Accesses to CVAL are not affected.

The workaround is to reread the system count registers until the value of the second
read is larger than the first one by less than 32, the system counter can be guaranteed
not to return wrong value twice by back-to-back read and the error value is always larger
than the correct one by 32. Writes to TVAL are replaced with an equivalent write to CVAL.

The workaround is enabled if the hisilicon,erratum-161601 property is found in
the timer node in the device tree. This can be overridden with the
clocksource.arm_arch_timer.hisilicon-161601 boot parameter, which allows KVM
users to enable the workaround until a mechanism is implemented to
automatically communicate this information.

Fix some description for fsl erratum a008585.

v2: Significant rework based on feedback, including seperate the fsl erratum a008585
    to another patch, update the erratum name and remove unwanted code.

v3: Significant rework based on feedback, including fix some alignment problem, make the
    #define __hisi_161601_read_reg to be private to the .c file instead of being globally
    visible, add more accurate annotation and modify a bit of logical format to enable
    arch_timer_read_ool_enabled, remove the kernel commandline parameter
    clocksource.arm_arch_timer.hisilicon-161601.

v5: Theoretically the erratum should not occur more than twice in succession when reading
    the system counter, but it is possible that some interrupts may lead to more than twice
    read errors, triggering the warning, so setting the number of retries to 50 which is far
    beyond the number of iterations the loop has been observed to take.

v6: Mark the hisi_161601_read_xxx_el0 with notrace, if CONFIG_FUNCTION_GRAPH_TRACER selected,
    hisi_161601_read_xxx_el0 will be related to ftrace_graph_caller which will calling sched_clock
    to read system counter again, it will cause the system stall into an endless loop.

Signed-off-by: Ding Tianhong <dingtianhong@huawei.com>
---
 Documentation/arm64/silicon-errata.txt |  1 +
 arch/arm64/include/asm/arch_timer.h    |  2 +-
 drivers/clocksource/Kconfig            |  9 +++++
 drivers/clocksource/arm_arch_timer.c   | 70 +++++++++++++++++++++++++++++++---
 4 files changed, 76 insertions(+), 6 deletions(-)

diff --git a/Documentation/arm64/silicon-errata.txt b/Documentation/arm64/silicon-errata.txt
index 405da11..1c1a95f 100644
--- a/Documentation/arm64/silicon-errata.txt
+++ b/Documentation/arm64/silicon-errata.txt
@@ -63,3 +63,4 @@ stable kernels.
 | Cavium         | ThunderX SMMUv2 | #27704          | N/A		       |
 |                |                 |                 |                         |
 | Freescale/NXP  | LS2080A/LS1043A | A-008585        | FSL_ERRATUM_A008585     |
+| Hisilicon      | Hip0{5,6,7}     | #161601         | HISILICON_ERRATUM_161601|
diff --git a/arch/arm64/include/asm/arch_timer.h b/arch/arm64/include/asm/arch_timer.h
index f882c7c..ebf4cde 100644
--- a/arch/arm64/include/asm/arch_timer.h
+++ b/arch/arm64/include/asm/arch_timer.h
@@ -29,7 +29,7 @@
 
 #include <clocksource/arm_arch_timer.h>
 
-#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585)
+#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585) || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
 extern struct static_key_false arch_timer_read_ool_enabled;
 #define needs_unstable_timer_counter_workaround() \
 	static_branch_unlikely(&arch_timer_read_ool_enabled)
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 4866f7a..162d820 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -335,6 +335,15 @@ config FSL_ERRATUM_A008585
 	  value").  The workaround will only be active if the
 	  fsl,erratum-a008585 property is found in the timer node.
 
+config HISILICON_ERRATUM_161601
+	bool "Workaround for Hisilicon Erratum 161601"
+	default y
+	depends on ARM_ARCH_TIMER && ARM64
+	help
+	  This option enables a workaround for Hisilicon Erratum
+	  161601. The workaround will be active if the hisilicon,erratum-161601
+	  property is found in the timer node.
+
 config ARM_GLOBAL_TIMER
 	bool "Support for the ARM global timer" if COMPILE_TEST
 	select CLKSRC_OF if OF
diff --git a/drivers/clocksource/arm_arch_timer.c b/drivers/clocksource/arm_arch_timer.c
index f9097b6..078d38f 100644
--- a/drivers/clocksource/arm_arch_timer.c
+++ b/drivers/clocksource/arm_arch_timer.c
@@ -95,15 +95,18 @@ static int __init early_evtstrm_cfg(char *buf)
  * Architected system timer support.
  */
 
-#ifdef CONFIG_FSL_ERRATUM_A008585
+#if CONFIG_FSL_ERRATUM_A008585 || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
 struct arch_timer_erratum_workaround *timer_unstable_counter_workaround = NULL;
 EXPORT_SYMBOL_GPL(timer_unstable_counter_workaround);
 
 #define        FSL_A008585	0x0001
+#define        HISILICON_161601	0x0002
 
 DEFINE_STATIC_KEY_FALSE(arch_timer_read_ool_enabled);
 EXPORT_SYMBOL_GPL(arch_timer_read_ool_enabled);
+#endif
 
+#ifdef CONFIG_FSL_ERRATUM_A008585
 /*
  * The number of retries is an arbitrary value well beyond the highest number
  * of iterations the loop has been observed to take.
@@ -145,6 +148,54 @@ static u64 notrace fsl_a008585_read_cntvct_el0(void)
 };
 #endif /* CONFIG_FSL_ERRATUM_A008585 */
 
+#ifdef CONFIG_HISILICON_ERRATUM_161601
+/*
+ * Verify whether the value of the second read is larger than the first by
+ * less than 32 is the only way to confirm the value is correct, so clear the
+ * lower 5 bits to check whether the difference is greater than 32 or not.
+ * Theoretically the erratum should not occur more than twice in succession
+ * when reading the system counter, but it is possible that some interrupts
+ * may lead to more than twice read errors, triggering the warning, so setting
+ * the number of retries far beyond the number of iterations the loop has been
+ * observed to take.
+ */
+#define __hisi_161601_read_reg(reg) ({				\
+	u64 _old, _new;						\
+	int _retries = 50;					\
+								\
+	do {							\
+		_old = read_sysreg(reg);			\
+		_new = read_sysreg(reg);			\
+		_retries--;					\
+	} while (unlikely((_new - _old) >> 5) && _retries);	\
+								\
+	WARN_ON_ONCE(!_retries);				\
+	_new;							\
+})
+
+static u32 notrace hisi_161601_read_cntp_tval_el0(void)
+{
+	return __hisi_161601_read_reg(cntp_tval_el0);
+}
+
+static u32 notrace hisi_161601_read_cntv_tval_el0(void)
+{
+	return __hisi_161601_read_reg(cntv_tval_el0);
+}
+
+static u64 notrace hisi_161601_read_cntvct_el0(void)
+{
+	return __hisi_161601_read_reg(cntvct_el0);
+}
+
+static struct arch_timer_erratum_workaround arch_timer_hisi_161601 = {
+	.erratum = HISILICON_161601,
+	.read_cntp_tval_el0 = hisi_161601_read_cntp_tval_el0,
+	.read_cntv_tval_el0 = hisi_161601_read_cntv_tval_el0,
+	.read_cntvct_el0 = hisi_161601_read_cntvct_el0,
+};
+#endif /* CONFIG_HISILICON_ERRATUM_161601 */
+
 static __always_inline
 void arch_timer_reg_write(int access, enum arch_timer_reg reg, u32 val,
 			  struct clock_event_device *clk)
@@ -294,7 +345,7 @@ static __always_inline void set_next_event(const int access, unsigned long evt,
 	arch_timer_reg_write(access, ARCH_TIMER_REG_CTRL, ctrl, clk);
 }
 
-#ifdef CONFIG_FSL_ERRATUM_A008585
+#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585) || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
 static __always_inline void erratum_set_next_event_generic(const int access,
 		unsigned long evt, struct clock_event_device *clk)
 {
@@ -358,7 +409,7 @@ static int arch_timer_set_next_event_phys_mem(unsigned long evt,
 
 static void erratum_workaround_set_sne(struct clock_event_device *clk)
 {
-#ifdef CONFIG_FSL_ERRATUM_A008585
+#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585) || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
 	if (!static_branch_unlikely(&arch_timer_read_ool_enabled))
 		return;
 
@@ -618,7 +669,7 @@ static void __init arch_counter_register(unsigned type)
 
 		clocksource_counter.archdata.vdso_direct = true;
 
-#ifdef CONFIG_FSL_ERRATUM_A008585
+#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585) || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
 		/*
 		 * Don't use the vdso fastpath if errata require using
 		 * the out-of-line counter accessor.
@@ -909,10 +960,19 @@ static int __init arch_timer_of_init(struct device_node *np)
 #ifdef CONFIG_FSL_ERRATUM_A008585
 	if (!timer_unstable_counter_workaround && of_property_read_bool(np, "fsl,erratum-a008585"))
 		timer_unstable_counter_workaround = &arch_timer_fsl_a008585;
+#endif
+
+#ifdef CONFIG_HISILICON_ERRATUM_161601
+	if (!timer_unstable_counter_workaround && of_property_read_bool(np, "hisilicon,erratum-161601"))
+		timer_unstable_counter_workaround = &arch_timer_hisi_161601;
+#endif
 
+#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585) || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
 	if (timer_unstable_counter_workaround) {
 		static_branch_enable(&arch_timer_read_ool_enabled);
-		pr_info("Enabling workaround for FSL erratum A-008585\n");
+		pr_info("Enabling workaround for %s\n",
+			timer_unstable_counter_workaround->erratum == FSL_A008585 ?
+			"FSL ERRATUM A-008585" : "HISILICON ERRATUM 161601");
 	}
 #endif
 
-- 
1.9.0

^ permalink raw reply related

* [PATCH v6 4/4] arm64: arch timer: Add timer erratum property for Hip05-d02 and Hip06-d03
From: Ding Tianhong @ 2017-01-05  5:31 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483594317-10732-1-git-send-email-dingtianhong@huawei.com>

Enable workaround for hisilicon erratum 161601 on Hip05-d02 and Hip06-d03 board.

Signed-off-by: Ding Tianhong <dingtianhong@huawei.com>
---
 arch/arm64/boot/dts/hisilicon/hip05.dtsi | 1 +
 arch/arm64/boot/dts/hisilicon/hip06.dtsi | 1 +
 2 files changed, 2 insertions(+)

diff --git a/arch/arm64/boot/dts/hisilicon/hip05.dtsi b/arch/arm64/boot/dts/hisilicon/hip05.dtsi
index 4b472a3..a8e9969 100644
--- a/arch/arm64/boot/dts/hisilicon/hip05.dtsi
+++ b/arch/arm64/boot/dts/hisilicon/hip05.dtsi
@@ -281,6 +281,7 @@
 			     <GIC_PPI 14 IRQ_TYPE_LEVEL_LOW>,
 			     <GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
 			     <GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
+		hisilicon,erratum-161601;
 	};
 
 	pmu {
diff --git a/arch/arm64/boot/dts/hisilicon/hip06.dtsi b/arch/arm64/boot/dts/hisilicon/hip06.dtsi
index a049b64..344e0f0 100644
--- a/arch/arm64/boot/dts/hisilicon/hip06.dtsi
+++ b/arch/arm64/boot/dts/hisilicon/hip06.dtsi
@@ -260,6 +260,7 @@
 			     <GIC_PPI 14 IRQ_TYPE_LEVEL_LOW>,
 			     <GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
 			     <GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
+		hisilicon,erratum-161601;
 	};
 
 	pmu {
-- 
1.9.0

^ permalink raw reply related

* [PATCH 03/22] iio: adc: add support for X-Powers AXP20X and AXP22X PMICs ADCs
From: Chen-Yu Tsai @ 2017-01-05  5:42 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170102163723.7939-4-quentin.schulz@free-electrons.com>

On Tue, Jan 3, 2017 at 12:37 AM, Quentin Schulz
<quentin.schulz@free-electrons.com> wrote:
> The X-Powers AXP20X and AXP22X PMICs have multiple ADCs. They expose the
> battery voltage, battery charge and discharge currents, AC-in and VBUS
> voltages and currents, 2 GPIOs muxable in ADC mode and PMIC temperature.
>
> This adds support for most of AXP20X and AXP22X ADCs.
>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> ---
>  drivers/iio/adc/Kconfig      |  10 +
>  drivers/iio/adc/Makefile     |   1 +
>  drivers/iio/adc/axp20x_adc.c | 490 +++++++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/axp20x.h   |   4 +
>  4 files changed, 505 insertions(+)
>  create mode 100644 drivers/iio/adc/axp20x_adc.c
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 38bc319..5c5b51f 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -154,6 +154,16 @@ config AT91_SAMA5D2_ADC
>           To compile this driver as a module, choose M here: the module will be
>           called at91-sama5d2_adc.
>
> +config AXP20X_ADC
> +       tristate "X-Powers AXP20X and AXP22X ADC driver"
> +       depends on MFD_AXP20X
> +       help
> +         Say yes here to have support for X-Powers power management IC (PMIC)
> +         AXP20X and AXP22X ADC devices.
> +
> +         To compile this driver as a module, choose M here: the module will be
> +         called axp20x_adc.
> +
>  config AXP288_ADC
>         tristate "X-Powers AXP288 ADC driver"
>         depends on MFD_AXP20X
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index d36c4be..f5c28a5 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_AD7887) += ad7887.o
>  obj-$(CONFIG_AD799X) += ad799x.o
>  obj-$(CONFIG_AT91_ADC) += at91_adc.o
>  obj-$(CONFIG_AT91_SAMA5D2_ADC) += at91-sama5d2_adc.o
> +obj-$(CONFIG_AXP20X_ADC) += axp20x_adc.o
>  obj-$(CONFIG_AXP288_ADC) += axp288_adc.o
>  obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o
>  obj-$(CONFIG_BERLIN2_ADC) += berlin2-adc.o
> diff --git a/drivers/iio/adc/axp20x_adc.c b/drivers/iio/adc/axp20x_adc.c
> new file mode 100644
> index 0000000..8df972a
> --- /dev/null
> +++ b/drivers/iio/adc/axp20x_adc.c
> @@ -0,0 +1,490 @@
> +/* ADC driver for AXP20X and AXP22X PMICs
> + *
> + * Copyright (c) 2016 Free Electrons NextThing Co.
> + *     Quentin Schulz <quentin.schulz@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it under
> + * the terms of the GNU General Public License version 2 as published by the
> + * Free Software Foundation.
> + *
> + */
> +
> +#include <linux/completion.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/thermal.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/mfd/axp20x.h>
> +
> +#define AXP20X_ADC_EN1_MASK                    GENMASK(7, 0)
> +
> +#define AXP20X_ADC_EN2_MASK                    (GENMASK(3, 2) | BIT(7))
> +#define AXP22X_ADC_EN1_MASK                    (GENMASK(7, 5) | BIT(0))
> +#define AXP20X_ADC_EN2_TEMP_ADC                        BIT(7)
> +#define AXP20X_ADC_EN2_GPIO0_ADC               BIT(3)
> +#define AXP20X_ADC_EN2_GPIO1_ADC               BIT(2)

The latter 3 individual bits aren't used anywhere.
Please remove them for now.

> +
> +#define AXP20X_GPIO10_IN_RANGE_GPIO0           BIT(0)
> +#define AXP20X_GPIO10_IN_RANGE_GPIO1           BIT(1)
> +#define AXP20X_GPIO10_IN_RANGE_GPIO0_VAL(x)    ((x) & BIT(0))
> +#define AXP20X_GPIO10_IN_RANGE_GPIO1_VAL(x)    (((x) & BIT(0)) << 1)
> +
> +#define AXP20X_ADC_RATE_MASK                   (3 << 6)
> +#define AXP20X_ADC_RATE_25HZ                   (0 << 6)
> +#define AXP20X_ADC_RATE_50HZ                   BIT(6)

Please be consistent with the format.

> +#define AXP20X_ADC_RATE_100HZ                  (2 << 6)
> +#define AXP20X_ADC_RATE_200HZ                  (3 << 6)
> +
> +#define AXP22X_ADC_RATE_100HZ                  (0 << 6)
> +#define AXP22X_ADC_RATE_200HZ                  BIT(6)
> +#define AXP22X_ADC_RATE_400HZ                  (2 << 6)
> +#define AXP22X_ADC_RATE_800HZ                  (3 << 6)

These are power-of-2 multiples of some base rate. May I suggest
a formula macro instead. Either way, you seem to be using only
one value. Will this be made configurable in the future?

> +
> +#define AXP20X_ADC_CHANNEL(_channel, _name, _type, _reg)       \
> +       {                                                       \
> +               .type = _type,                                  \
> +               .indexed = 1,                                   \
> +               .channel = _channel,                            \
> +               .address = _reg,                                \
> +               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |  \
> +                                     BIT(IIO_CHAN_INFO_SCALE), \
> +               .datasheet_name = _name,                        \
> +       }
> +
> +#define AXP20X_ADC_CHANNEL_OFFSET(_channel, _name, _type, _reg) \
> +       {                                                       \
> +               .type = _type,                                  \
> +               .indexed = 1,                                   \
> +               .channel = _channel,                            \
> +               .address = _reg,                                \
> +               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |  \
> +                                     BIT(IIO_CHAN_INFO_SCALE) |\
> +                                     BIT(IIO_CHAN_INFO_OFFSET),\
> +               .datasheet_name = _name,                        \
> +       }
> +
> +struct axp20x_adc_iio {
> +       struct iio_dev          *indio_dev;
> +       struct regmap           *regmap;
> +};
> +
> +enum axp20x_adc_channel {
> +       AXP20X_ACIN_V = 0,
> +       AXP20X_ACIN_I,
> +       AXP20X_VBUS_V,
> +       AXP20X_VBUS_I,
> +       AXP20X_TEMP_ADC,

PMIC_TEMP would be better. And please save a slot for TS input.

> +       AXP20X_GPIO0_V,
> +       AXP20X_GPIO1_V,

Please skip a slot for "battery instantaneous power".

> +       AXP20X_BATT_V,
> +       AXP20X_BATT_CHRG_I,
> +       AXP20X_BATT_DISCHRG_I,
> +       AXP20X_IPSOUT_V,
> +};
> +
> +enum axp22x_adc_channel {
> +       AXP22X_TEMP_ADC = 0,

Same comments as AXP20X_TEMP_ADC.

> +       AXP22X_BATT_V,
> +       AXP22X_BATT_CHRG_I,
> +       AXP22X_BATT_DISCHRG_I,
> +};

Shouldn't these channel numbers be exported as part of the device tree
bindings? At the very least, they shouldn't be changed.

Also please add a comment saying that the channels are numbered
in the order of their respective registers, and not the table
describing the ADCs in the datasheet (9.7 Signal Capture for AXP209
and 9.5 E-Gauge for AXP221).

> +
> +static const struct iio_chan_spec axp20x_adc_channels[] = {
> +       AXP20X_ADC_CHANNEL(AXP20X_ACIN_V, "acin_v", IIO_VOLTAGE,
> +                          AXP20X_ACIN_V_ADC_H),
> +       AXP20X_ADC_CHANNEL(AXP20X_ACIN_I, "acin_i", IIO_CURRENT,
> +                          AXP20X_ACIN_I_ADC_H),
> +       AXP20X_ADC_CHANNEL(AXP20X_VBUS_V, "vbus_v", IIO_VOLTAGE,
> +                          AXP20X_VBUS_V_ADC_H),
> +       AXP20X_ADC_CHANNEL(AXP20X_VBUS_I, "vbus_i", IIO_CURRENT,
> +                          AXP20X_VBUS_I_ADC_H),
> +       AXP20X_ADC_CHANNEL_OFFSET(AXP20X_TEMP_ADC, "temp_adc", IIO_TEMP,
> +                                 AXP20X_TEMP_ADC_H),
> +       AXP20X_ADC_CHANNEL_OFFSET(AXP20X_GPIO0_V, "gpio0_v", IIO_VOLTAGE,
> +                                 AXP20X_GPIO0_V_ADC_H),
> +       AXP20X_ADC_CHANNEL_OFFSET(AXP20X_GPIO1_V, "gpio1_v", IIO_VOLTAGE,
> +                                 AXP20X_GPIO1_V_ADC_H),
> +       AXP20X_ADC_CHANNEL(AXP20X_BATT_V, "batt_v", IIO_VOLTAGE,
> +                          AXP20X_BATT_V_H),
> +       AXP20X_ADC_CHANNEL(AXP20X_BATT_CHRG_I, "batt_chrg_i", IIO_CURRENT,
> +                          AXP20X_BATT_CHRG_I_H),
> +       AXP20X_ADC_CHANNEL(AXP20X_BATT_DISCHRG_I, "batt_dischrg_i", IIO_CURRENT,
> +                          AXP20X_BATT_DISCHRG_I_H),
> +       AXP20X_ADC_CHANNEL(AXP20X_IPSOUT_V, "ipsout_v", IIO_VOLTAGE,
> +                          AXP20X_IPSOUT_V_HIGH_H),
> +};
> +
> +static const struct iio_chan_spec axp22x_adc_channels[] = {
> +       AXP20X_ADC_CHANNEL_OFFSET(AXP22X_TEMP_ADC, "temp_adc", IIO_TEMP,
> +                                 AXP22X_TEMP_ADC_H),
> +       AXP20X_ADC_CHANNEL(AXP22X_BATT_V, "batt_v", IIO_VOLTAGE,
> +                          AXP20X_BATT_V_H),
> +       AXP20X_ADC_CHANNEL(AXP22X_BATT_CHRG_I, "batt_chrg_i", IIO_CURRENT,
> +                          AXP20X_BATT_CHRG_I_H),
> +       AXP20X_ADC_CHANNEL(AXP22X_BATT_DISCHRG_I, "batt_dischrg_i", IIO_CURRENT,
> +                          AXP20X_BATT_DISCHRG_I_H),
> +};
> +
> +static int axp20x_adc_read_raw(struct iio_dev *indio_dev,
> +                              struct iio_chan_spec const *channel, int *val,
> +                              int *val2)
> +{
> +       struct axp20x_adc_iio *info = iio_priv(indio_dev);
> +       int size = 12, ret;
> +
> +       switch (channel->channel) {
> +       case AXP20X_BATT_DISCHRG_I:
> +               size = 13;
> +       case AXP20X_ACIN_V:
> +       case AXP20X_ACIN_I:
> +       case AXP20X_VBUS_V:
> +       case AXP20X_VBUS_I:
> +       case AXP20X_TEMP_ADC:
> +       case AXP20X_BATT_V:
> +       case AXP20X_BATT_CHRG_I:
> +       case AXP20X_IPSOUT_V:
> +       case AXP20X_GPIO0_V:
> +       case AXP20X_GPIO1_V:
> +               ret = axp20x_read_variable_width(info->regmap, channel->address,
> +                                                size);
> +               if (ret < 0)
> +                       return ret;
> +               *val = ret;
> +               return IIO_VAL_INT;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return -EINVAL;
> +}
> +
> +static int axp22x_adc_read_raw(struct iio_dev *indio_dev,
> +                              struct iio_chan_spec const *channel, int *val,
> +                              int *val2)
> +{
> +       struct axp20x_adc_iio *info = iio_priv(indio_dev);
> +       int size = 12, ret;
> +
> +       switch (channel->channel) {
> +       case AXP22X_BATT_DISCHRG_I:
> +               size = 13;
> +       case AXP22X_TEMP_ADC:
> +       case AXP22X_BATT_V:
> +       case AXP22X_BATT_CHRG_I:

According to the datasheet, AXP22X_BATT_CHRG_I is also 13 bits wide.

> +               ret = axp20x_read_variable_width(info->regmap, channel->address,
> +                                                size);
> +               if (ret < 0)
> +                       return ret;
> +               *val = ret;
> +               return IIO_VAL_INT;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return -EINVAL;
> +}
> +
> +static int axp20x_adc_scale(int channel, int *val, int *val2)
> +{
> +       switch (channel) {
> +       case AXP20X_ACIN_V:
> +       case AXP20X_VBUS_V:
> +               *val = 1;
> +               *val2 = 700000;
> +               return IIO_VAL_INT_PLUS_MICRO;
> +
> +       case AXP20X_ACIN_I:
> +               *val = 0;
> +               *val2 = 625000;
> +               return IIO_VAL_INT_PLUS_MICRO;
> +
> +       case AXP20X_VBUS_I:
> +               *val = 0;
> +               *val2 = 375000;
> +               return IIO_VAL_INT_PLUS_MICRO;
> +
> +       case AXP20X_TEMP_ADC:
> +               *val = 100;
> +               return IIO_VAL_INT;
> +
> +       case AXP20X_GPIO0_V:
> +       case AXP20X_GPIO1_V:
> +               *val = 0;
> +               *val2 = 500000;
> +               return IIO_VAL_INT_PLUS_MICRO;
> +
> +       case AXP20X_BATT_V:
> +               *val = 1;
> +               *val2 = 100000;
> +               return IIO_VAL_INT_PLUS_MICRO;
> +
> +       case AXP20X_BATT_DISCHRG_I:
> +       case AXP20X_BATT_CHRG_I:
> +               *val = 0;
> +               *val2 = 500000;
> +               return IIO_VAL_INT_PLUS_MICRO;
> +
> +       case AXP20X_IPSOUT_V:
> +               *val = 1;
> +               *val2 = 400000;
> +               return IIO_VAL_INT_PLUS_MICRO;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return -EINVAL;
> +}
> +
> +static int axp22x_adc_scale(int channel, int *val, int *val2)
> +{
> +       switch (channel) {
> +       case AXP22X_TEMP_ADC:
> +               *val = 100;
> +               return IIO_VAL_INT;
> +
> +       case AXP22X_BATT_V:
> +               *val = 1;
> +               *val2 = 100000;
> +               return IIO_VAL_INT_PLUS_MICRO;
> +
> +       case AXP22X_BATT_DISCHRG_I:
> +       case AXP22X_BATT_CHRG_I:
> +               *val = 0;
> +               *val2 = 500000;
> +               return IIO_VAL_INT_PLUS_MICRO;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return -EINVAL;
> +}
> +
> +static int axp20x_adc_offset(struct iio_dev *indio_dev, int channel, int *val)
> +{
> +       struct axp20x_adc_iio *info = iio_priv(indio_dev);
> +       int ret, reg;
> +
> +       switch (channel) {
> +       case AXP20X_TEMP_ADC:
> +               *val = -1447;
> +               return IIO_VAL_INT;
> +
> +       case AXP20X_GPIO0_V:
> +       case AXP20X_GPIO1_V:
> +               ret = regmap_read(info->regmap, AXP20X_GPIO10_IN_RANGE, &reg);
> +               if (ret < 0)
> +                       return ret;
> +
> +               if (channel == AXP20X_GPIO0_V)
> +                       *val = reg & AXP20X_GPIO10_IN_RANGE_GPIO0;
> +               else
> +                       *val = reg & AXP20X_GPIO10_IN_RANGE_GPIO1;
> +
> +               *val = !!(*val) * 700000;
> +
> +               return IIO_VAL_INT;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return -EINVAL;
> +}
> +
> +static int axp20x_read_raw(struct iio_dev *indio_dev,
> +                          struct iio_chan_spec const *chan, int *val,
> +                          int *val2, long mask)
> +{
> +       switch (mask) {
> +       case IIO_CHAN_INFO_OFFSET:
> +               return axp20x_adc_offset(indio_dev, chan->channel, val);
> +
> +       case IIO_CHAN_INFO_SCALE:
> +               return axp20x_adc_scale(chan->channel, val, val2);
> +
> +       case IIO_CHAN_INFO_RAW:
> +               return axp20x_adc_read_raw(indio_dev, chan, val, val2);
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return -EINVAL;
> +}
> +
> +static int axp22x_read_raw(struct iio_dev *indio_dev,
> +                          struct iio_chan_spec const *chan, int *val,
> +                          int *val2, long mask)
> +{
> +       switch (mask) {
> +       case IIO_CHAN_INFO_OFFSET:
> +               *val = -2667;

Datasheet says -267.7 C, or -2677 here.

> +               return IIO_VAL_INT;
> +
> +       case IIO_CHAN_INFO_SCALE:
> +               return axp22x_adc_scale(chan->channel, val, val2);
> +
> +       case IIO_CHAN_INFO_RAW:
> +               return axp22x_adc_read_raw(indio_dev, chan, val, val2);
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return -EINVAL;
> +}
> +
> +static int axp20x_write_raw(struct iio_dev *indio_dev,
> +                           struct iio_chan_spec const *chan, int val, int val2,
> +                           long mask)
> +{
> +       struct axp20x_adc_iio *info = iio_priv(indio_dev);
> +
> +       /*
> +        * The AXP20X PMIC allows the user to choose between 0V and 0.7V offsets
> +        * for (independently) GPIO0 and GPIO1 when in ADC mode.
> +        */
> +       if (mask != IIO_CHAN_INFO_OFFSET)
> +               return -EINVAL;
> +
> +       if (chan->channel != AXP20X_GPIO0_V && chan->channel != AXP20X_GPIO1_V)
> +               return -EINVAL;
> +
> +       if (val != 0 && val != 700000)
> +               return -EINVAL;
> +
> +       if (chan->channel == AXP20X_GPIO0_V)
> +               return regmap_update_bits(info->regmap, AXP20X_GPIO10_IN_RANGE,
> +                                         AXP20X_GPIO10_IN_RANGE_GPIO0,
> +                                         AXP20X_GPIO10_IN_RANGE_GPIO0_VAL(!!val));
> +
> +       return regmap_update_bits(info->regmap, AXP20X_GPIO10_IN_RANGE,
> +                                 AXP20X_GPIO10_IN_RANGE_GPIO1,
> +                                 AXP20X_GPIO10_IN_RANGE_GPIO1_VAL(!!val));
> +}
> +
> +static const struct iio_info axp20x_adc_iio_info = {
> +       .read_raw = axp20x_read_raw,
> +       .write_raw = axp20x_write_raw,
> +       .driver_module = THIS_MODULE,
> +};
> +
> +static const struct iio_info axp22x_adc_iio_info = {
> +       .read_raw = axp22x_read_raw,
> +       .driver_module = THIS_MODULE,
> +};
> +
> +static const struct of_device_id axp20x_adc_of_match[] = {
> +       { .compatible = "x-powers,axp209-adc", .data = (void *)AXP209_ID, },
> +       { .compatible = "x-powers,axp221-adc", .data = (void *)AXP221_ID, },
> +       { /* sentinel */ },
> +};
> +
> +static int axp20x_probe(struct platform_device *pdev)
> +{
> +       struct axp20x_adc_iio *info;
> +       struct iio_dev *indio_dev;
> +       struct axp20x_dev *axp20x_dev;
> +       int ret, axp20x_id;
> +
> +       axp20x_dev = dev_get_drvdata(pdev->dev.parent);
> +
> +       indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
> +       if (!indio_dev)
> +               return -ENOMEM;
> +
> +       info = iio_priv(indio_dev);
> +       platform_set_drvdata(pdev, indio_dev);
> +
> +       info->regmap = axp20x_dev->regmap;
> +       info->indio_dev = indio_dev;
> +       indio_dev->name = dev_name(&pdev->dev);
> +       indio_dev->dev.parent = &pdev->dev;
> +       indio_dev->dev.of_node = pdev->dev.of_node;
> +       indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +       axp20x_id = (int)of_device_get_match_data(&pdev->dev);
> +
> +       switch (axp20x_id) {
> +       case AXP209_ID:
> +               indio_dev->info = &axp20x_adc_iio_info;
> +               indio_dev->num_channels = ARRAY_SIZE(axp20x_adc_channels);
> +               indio_dev->channels = axp20x_adc_channels;
> +
> +               /* Enable the ADCs on IP */
> +               regmap_write(info->regmap, AXP20X_ADC_EN1, AXP20X_ADC_EN1_MASK);
> +
> +               /* Enable GPIO0/1 and internal temperature ADCs */
> +               regmap_update_bits(info->regmap, AXP20X_ADC_EN2,
> +                                  AXP20X_ADC_EN2_MASK, AXP20X_ADC_EN2_MASK);
> +
> +               /* Configure ADCs rate */
> +               regmap_update_bits(info->regmap, AXP20X_ADC_RATE,
> +                                  AXP20X_ADC_RATE_MASK, AXP20X_ADC_RATE_50HZ);
> +               break;
> +
> +       case AXP221_ID:
> +               indio_dev->info = &axp22x_adc_iio_info;
> +               indio_dev->num_channels = ARRAY_SIZE(axp22x_adc_channels);
> +               indio_dev->channels = axp22x_adc_channels;
> +
> +               /* Enable the ADCs on IP */
> +               regmap_write(info->regmap, AXP20X_ADC_EN1, AXP22X_ADC_EN1_MASK);
> +
> +               /* Configure ADCs rate */
> +               regmap_update_bits(info->regmap, AXP20X_ADC_RATE,
> +                                  AXP20X_ADC_RATE_MASK, AXP22X_ADC_RATE_200HZ);
> +               break;
> +
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       ret = devm_iio_device_register(&pdev->dev, indio_dev);
> +       if (ret < 0) {
> +               dev_err(&pdev->dev, "could not register the device\n");
> +               regmap_write(info->regmap, AXP20X_ADC_EN1, 0);
> +               regmap_write(info->regmap, AXP20X_ADC_EN2, 0);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int axp20x_remove(struct platform_device *pdev)
> +{
> +       struct axp20x_adc_iio *info;
> +       struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +
> +       info = iio_priv(indio_dev);

Nit: you could just reverse the 2 declarations above and join this
line after struct axp20x_adc_iio *info;

> +       regmap_write(info->regmap, AXP20X_ADC_EN1, 0);
> +       regmap_write(info->regmap, AXP20X_ADC_EN2, 0);

The existing VBUS power supply driver enables the VBUS ADC bits itself,
and does not check them later on. This means if one were to remove this
axp20x-adc module, the voltage/current readings in the VBUS power supply
would be invalid. Some sort of workaround would be needed here in this
driver of the VBUS driver.

> +
> +       return 0;
> +}
> +
> +static struct platform_driver axp20x_adc_driver = {
> +       .driver = {
> +               .name = "axp20x-adc",
> +               .of_match_table = axp20x_adc_of_match,
> +       },
> +       .probe = axp20x_probe,
> +       .remove = axp20x_remove,
> +};
> +
> +module_platform_driver(axp20x_adc_driver);
> +
> +MODULE_DESCRIPTION("ADC driver for AXP20X and AXP22X PMICs");
> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/mfd/axp20x.h b/include/linux/mfd/axp20x.h
> index a4860bc..650c6f6 100644
> --- a/include/linux/mfd/axp20x.h
> +++ b/include/linux/mfd/axp20x.h
> @@ -150,6 +150,10 @@ enum {
>  #define AXP20X_VBUS_I_ADC_L            0x5d
>  #define AXP20X_TEMP_ADC_H              0x5e
>  #define AXP20X_TEMP_ADC_L              0x5f
> +
> +#define AXP22X_TEMP_ADC_H              0x56
> +#define AXP22X_TEMP_ADC_L              0x57
> +

This is in the wrong patch. Also we already have

/* AXP22X specific registers */
#define AXP22X_PMIC_ADC_H               0x56
#define AXP22X_PMIC_ADC_L               0x57
#define AXP22X_TS_ADC_H                 0x58
#define AXP22X_TS_ADC_L                 0x59

If you want, you could just rename them to be consistent.

Regards
ChenYu

>  #define AXP20X_TS_IN_H                 0x62
>  #define AXP20X_TS_IN_L                 0x63
>  #define AXP20X_GPIO0_V_ADC_H           0x64
> --
> 2.9.3
>

^ permalink raw reply

* [PATCH 04/22] mfd: axp20x: add ADC cells for AXP20X and AXP22X PMICs
From: Chen-Yu Tsai @ 2017-01-05  5:51 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170104115637.GC24058@dell>

On Wed, Jan 4, 2017 at 7:56 PM, Lee Jones <lee.jones@linaro.org> wrote:
> On Wed, 04 Jan 2017, Lee Jones wrote:
>
>> On Mon, 02 Jan 2017, Quentin Schulz wrote:
>>
>> > This adds the AXP20X/AXP22x ADCs driver to the mfd cells of the AXP209,
>> > AXP221 and AXP223 MFD.
>> >
>> > Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>> > ---
>> >  drivers/mfd/axp20x.c | 9 +++++++++
>> >  1 file changed, 9 insertions(+)
>>
>> Applied, thanks.
>
> Whoops, wrong key combo!  Should have been:
>
> For my own reference:
>   Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>

Acked-by: Chen-Yu Tsai <wens@csie.org>

>
>> > diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
>> > index a33db5e..31a84d81 100644
>> > --- a/drivers/mfd/axp20x.c
>> > +++ b/drivers/mfd/axp20x.c
>> > @@ -582,6 +582,9 @@ static struct mfd_cell axp20x_cells[] = {
>> >     }, {
>> >             .name           = "axp20x-regulator",
>> >     }, {
>> > +           .name           = "axp20x-adc",
>> > +           .of_compatible  = "x-powers,axp209-adc",
>> > +   }, {
>> >             .name           = "axp20x-ac-power-supply",
>> >             .of_compatible  = "x-powers,axp202-ac-power-supply",
>> >             .num_resources  = ARRAY_SIZE(axp20x_ac_power_supply_resources),
>> > @@ -602,6 +605,9 @@ static struct mfd_cell axp221_cells[] = {
>> >     }, {
>> >             .name           = "axp20x-regulator",
>> >     }, {
>> > +           .name           = "axp20x-adc",
>> > +           .of_compatible  = "x-powers,axp221-adc"
>> > +   }, {
>> >             .name           = "axp20x-usb-power-supply",
>> >             .of_compatible  = "x-powers,axp221-usb-power-supply",
>> >             .num_resources  = ARRAY_SIZE(axp22x_usb_power_supply_resources),
>> > @@ -615,6 +621,9 @@ static struct mfd_cell axp223_cells[] = {
>> >             .num_resources          = ARRAY_SIZE(axp22x_pek_resources),
>> >             .resources              = axp22x_pek_resources,
>> >     }, {
>> > +           .name           = "axp20x-adc",
>> > +           .of_compatible  = "x-powers,axp221-adc"
>> > +   }, {
>> >             .name                   = "axp20x-regulator",
>> >     }, {
>> >             .name           = "axp20x-usb-power-supply",
>>
>
> --
> Lee Jones
> Linaro STMicroelectronics Landing Team Lead
> Linaro.org ? Open source software for ARM SoCs
> Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply

* [PATCH 05/22] ARM: dtsi: axp209: add AXP209 ADC subnode
From: Chen-Yu Tsai @ 2017-01-05  5:51 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170102163723.7939-6-quentin.schulz@free-electrons.com>

On Tue, Jan 3, 2017 at 12:37 AM, Quentin Schulz
<quentin.schulz@free-electrons.com> wrote:
> X-Powers AXP209 PMIC has multiple ADCs, each one exposing data from the
> different power supplies connected to the PMIC.
>
> This adds the ADC subnode for AXP20X PMIC.
>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> ---
>  arch/arm/boot/dts/axp209.dtsi | 5 +++++
>  1 file changed, 5 insertions(+)
>
> diff --git a/arch/arm/boot/dts/axp209.dtsi b/arch/arm/boot/dts/axp209.dtsi
> index 675bb0f..2a4e8ee 100644
> --- a/arch/arm/boot/dts/axp209.dtsi
> +++ b/arch/arm/boot/dts/axp209.dtsi
> @@ -53,6 +53,11 @@
>         interrupt-controller;
>         #interrupt-cells = <1>;
>
> +       axp209_adc: axp209_adc {

Node name should be generic. Please change it to "adc".

ChenYu

> +               compatible = "x-powers,axp209-adc";
> +               #io-channel-cells = <1>;
> +       };
> +
>         axp_gpio: gpio {
>                 compatible = "x-powers,axp209-gpio";
>                 gpio-controller;
> --
> 2.9.3
>

^ permalink raw reply

* [PATCH 06/22] ARM: dtsi: axp22x: add AXP22X ADC subnode
From: Chen-Yu Tsai @ 2017-01-05  5:52 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170102163723.7939-7-quentin.schulz@free-electrons.com>

On Tue, Jan 3, 2017 at 12:37 AM, Quentin Schulz
<quentin.schulz@free-electrons.com> wrote:
> X-Powers AXP22X PMIC has multiple ADCs, each one exposing data from the
> different power supplies connected to the PMIC.
>
> This adds the ADC subnode for AXP22X PMIC.
>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> ---
>  arch/arm/boot/dts/axp22x.dtsi | 5 +++++
>  1 file changed, 5 insertions(+)
>
> diff --git a/arch/arm/boot/dts/axp22x.dtsi b/arch/arm/boot/dts/axp22x.dtsi
> index 458b668..a2c4401 100644
> --- a/arch/arm/boot/dts/axp22x.dtsi
> +++ b/arch/arm/boot/dts/axp22x.dtsi
> @@ -52,6 +52,11 @@
>         interrupt-controller;
>         #interrupt-cells = <1>;
>
> +       axp221_adc: axp221_adc {

Same as the last patch. Please change to "adc".

ChenYu

> +               compatible = "x-powers,axp221-adc";
> +               #io-channel-cells = <1>;
> +       };
> +
>         regulators {
>                 /* Default work frequency for buck regulators */
>                 x-powers,dcdc-freq = <3000>;
> --
> 2.9.3
>

^ permalink raw reply

* [PATCH v11 0/8] power: add power sequence library
From: Peter Chen @ 2017-01-05  6:01 UTC (permalink / raw)
  To: linux-arm-kernel

Hi all,

This is a follow-up for my last power sequence framework patch set [1].
According to Rob Herring and Ulf Hansson's comments[2]. The kinds of
power sequence instances will be added at postcore_initcall, the match
criteria is compatible string first, if the compatible string is not
matched between dts and library, it will try to use generic power sequence.
	 
The host driver just needs to call of_pwrseq_on/of_pwrseq_off
if only one power sequence instance is needed, for more power sequences
are used, using of_pwrseq_on_list/of_pwrseq_off_list instead (eg, USB hub driver).

In future, if there are special power sequence requirements, the special
power sequence library can be created.

This patch set is tested on i.mx6 sabresx evk using a dts change, I use
two hot-plug devices to simulate this use case, the related binding
change is updated at patch [1/6], The udoo board changes were tested
using my last power sequence patch set.[3]

Except for hard-wired MMC and USB devices, I find the USB ULPI PHY also
need to power on itself before it can be found by ULPI bus.

[1] http://www.spinics.net/lists/linux-usb/msg142755.html
[2] http://www.spinics.net/lists/linux-usb/msg143106.html
[3] http://www.spinics.net/lists/linux-usb/msg142815.html

Changes for v11:
- Fix warning: (USB) selects POWER_SEQUENCE which has unmet direct dependencies (OF)
- Delete redundant copyright statement.
- Change pr_warn to pr_debug at wrseq_find_available_instance
- Refine kerneldoc
- %s/ENONET/ENOENT 
- Allocate pwrseq list node before than carry out power sequence on 
- Add mutex_lock/mutex_lock for pwrseq node browse at pwrseq_find_available_instance
- Add pwrseq_suspend/resume for API both single instance and list 
- Add .pwrseq_suspend/resume for pwrseq_generic.c
- Add pwrseq_suspend_list and pwrseq_resume_list for USB hub suspend
  and resume routine

Changes for v10:
- Improve the kernel-doc for power sequence core, including exported APIs and
  main structure. [Patch 2/8]
- Change Kconfig, and let the user choose power sequence. [Patch 2/8]
- Delete EXPORT_SYMBOL and change related APIs as local, these APIs do not
  be intended to export currently. [Patch 2/8]
- Selete POWER_SEQUENCE at USB core's Kconfig. [Patch 4/8]

Changes for v9:
- Add Vaibhav Hiremath's reviewed-by [Patch 4/8]
- Rebase to v4.9-rc1

Changes for v8:
- Allocate one extra pwrseq instance if pwrseq_get has succeed, it can avoid
  preallocate instances problem which the number of instance is decided at
  compile time, thanks for Heiko Stuebner's suggestion [Patch 2/8]
- Delete pwrseq_compatible_sample.c which is the demo purpose to show compatible
  match method. [Patch 2/8]
- Add Maciej S. Szmigiero's tested-by. [Patch 7/8]

Changes for v7:
- Create kinds of power sequence instance at postcore_initcall, and match
  the instance with node using compatible string, the beneit of this is
  the host driver doesn't need to consider which pwrseq instance needs
  to be used, and pwrseq core will match it, however, it eats some memories
  if less power sequence instances are used. [Patch 2/8]
- Add pwrseq_compatible_sample.c to test match pwrseq using device_id. [Patch 2/8]
- Fix the comments Vaibhav Hiremath adds for error path for clock and do not
  use device_node for parameters at pwrseq_on. [Patch 2/8]
- Simplify the caller to use power sequence, follows Alan's commnets [Patch 4/8]
- Tested three pwrseq instances together using both specific compatible string and
  generic libraries.

Changes for v6:
- Add Matthias Kaehlcke's Reviewed-by and Tested-by. (patch [2/6])
- Change chipidea core of_node assignment for coming user. (patch [5/6])
- Applies Joshua Clayton's three dts changes for two boards,
  the USB device's reg has only #address-cells, but without #size-cells.

Changes for v5:
- Delete pwrseq_register/pwrseq_unregister, which is useless currently
- Fix the linker error when the pwrseq user is compiled as module

Changes for v4:
- Create the patch on next-20160722 
- Fix the of_node is not NULL after chipidea driver is unbinded [Patch 5/6]
- Using more friendly wait method for reset gpio [Patch 2/6]
- Support multiple input clocks [Patch 2/6]
- Add Rob Herring's ack for DT changes
- Add Joshua Clayton's Tested-by

Changes for v3:
- Delete "power-sequence" property at binding-doc, and change related code
  at both library and user code.
- Change binding-doc example node name with Rob's comments
- of_get_named_gpio_flags only gets the gpio, but without setting gpio flags,
  add additional code request gpio with proper gpio flags
- Add Philipp Zabel's Ack and MAINTAINER's entry

Changes for v2:
- Delete "pwrseq" prefix and clock-names for properties at dt binding
- Should use structure not but its pointer for kzalloc
- Since chipidea core has no of_node, let core's of_node equals glue
  layer's at core's probe

Joshua Clayton (2):
  ARM: dts: imx6qdl: Enable usb node children with <reg>
  ARM: dts: imx6q-evi: Fix onboard hub reset line

Peter Chen (6):
  binding-doc: power: pwrseq-generic: add binding doc for generic power
    sequence library
  power: add power sequence library
  binding-doc: usb: usb-device: add optional properties for power
    sequence
  usb: core: add power sequence handling for USB devices
  usb: chipidea: let chipidea core device of_node equal's glue layer
    device of_node
  ARM: dts: imx6qdl-udoo.dtsi: fix onboard USB HUB property

 .../bindings/power/pwrseq/pwrseq-generic.txt       |  48 +++
 .../devicetree/bindings/usb/usb-device.txt         |  10 +-
 MAINTAINERS                                        |   9 +
 arch/arm/boot/dts/imx6q-evi.dts                    |  25 +-
 arch/arm/boot/dts/imx6qdl-udoo.dtsi                |  26 +-
 arch/arm/boot/dts/imx6qdl.dtsi                     |   6 +
 drivers/power/Kconfig                              |   1 +
 drivers/power/Makefile                             |   1 +
 drivers/power/pwrseq/Kconfig                       |  20 ++
 drivers/power/pwrseq/Makefile                      |   2 +
 drivers/power/pwrseq/core.c                        | 335 +++++++++++++++++++++
 drivers/power/pwrseq/pwrseq_generic.c              | 224 ++++++++++++++
 drivers/usb/Kconfig                                |   1 +
 drivers/usb/chipidea/core.c                        |  27 +-
 drivers/usb/core/hub.c                             |  48 ++-
 drivers/usb/core/hub.h                             |   1 +
 include/linux/power/pwrseq.h                       |  81 +++++
 17 files changed, 823 insertions(+), 42 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt
 create mode 100644 drivers/power/pwrseq/Kconfig
 create mode 100644 drivers/power/pwrseq/Makefile
 create mode 100644 drivers/power/pwrseq/core.c
 create mode 100644 drivers/power/pwrseq/pwrseq_generic.c
 create mode 100644 include/linux/power/pwrseq.h

-- 
2.7.4

^ permalink raw reply

* [PATCH v11 1/8] binding-doc: power: pwrseq-generic: add binding doc for generic power sequence library
From: Peter Chen @ 2017-01-05  6:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>

Add binding doc for generic power sequence library.

Signed-off-by: Peter Chen <peter.chen@nxp.com>
Acked-by: Philipp Zabel <p.zabel@pengutronix.de>
Acked-by: Rob Herring <robh@kernel.org>
---
 .../bindings/power/pwrseq/pwrseq-generic.txt       | 48 ++++++++++++++++++++++
 1 file changed, 48 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt

diff --git a/Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt b/Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt
new file mode 100644
index 0000000..ebf0d47
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt
@@ -0,0 +1,48 @@
+The generic power sequence library
+
+Some hard-wired devices (eg USB/MMC) need to do power sequence before
+the device can be enumerated on the bus, the typical power sequence
+like: enable USB PHY clock, toggle reset pin, etc. But current
+Linux device driver lacks of such code to do it, it may cause some
+hard-wired devices works abnormal or can't be recognized by
+controller at all. The power sequence will be done before this device
+can be found at the bus.
+
+The power sequence properties is under the device node.
+
+Optional properties:
+- clocks: the input clocks for device.
+- reset-gpios: Should specify the GPIO for reset.
+- reset-duration-us: the duration in microsecond for assert reset signal.
+
+Below is the example of USB power sequence properties on USB device
+nodes which have two level USB hubs.
+
+&usbotg1 {
+	vbus-supply = <&reg_usb_otg1_vbus>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_usb_otg1_id>;
+	status = "okay";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+	genesys: hub at 1 {
+		compatible = "usb5e3,608";
+		reg = <1>;
+
+		clocks = <&clks IMX6SX_CLK_CKO>;
+		reset-gpios = <&gpio4 5 GPIO_ACTIVE_LOW>; /* hub reset pin */
+		reset-duration-us = <10>;
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+		asix: ethernet at 1 {
+			compatible = "usbb95,1708";
+			reg = <1>;
+
+			clocks = <&clks IMX6SX_CLK_IPG>;
+			reset-gpios = <&gpio4 6 GPIO_ACTIVE_LOW>; /* ethernet_rst */
+			reset-duration-us = <15>;
+		};
+	};
+};
-- 
2.7.4

^ permalink raw reply related

* [PATCH v11 2/8] power: add power sequence library
From: Peter Chen @ 2017-01-05  6:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>

We have an well-known problem that the device needs to do some power
sequence before it can be recognized by related host, the typical
example like hard-wired mmc devices and usb devices.

This power sequence is hard to be described at device tree and handled by
related host driver, so we have created a common power sequence
library to cover this requirement. The core code has supplied
some common helpers for host driver, and individual power sequence
libraries handle kinds of power sequence for devices. The pwrseq
librares always need to allocate extra instance for compatible
string match.

pwrseq_generic is intended for general purpose of power sequence, which
handles gpios and clocks currently, and can cover other controls in
future. The host driver just needs to call of_pwrseq_on/of_pwrseq_off
if only one power sequence is needed, else call of_pwrseq_on_list
/of_pwrseq_off_list instead (eg, USB hub driver).

For new power sequence library, it can add its compatible string
to pwrseq_of_match_table, then the pwrseq core will match it with
DT's, and choose this library at runtime.

Signed-off-by: Peter Chen <peter.chen@nxp.com>
Tested-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
Tested-by Joshua Clayton <stillcompiling@gmail.com>
Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
Tested-by: Matthias Kaehlcke <mka@chromium.org>
---
 MAINTAINERS                           |   9 +
 drivers/power/Kconfig                 |   1 +
 drivers/power/Makefile                |   1 +
 drivers/power/pwrseq/Kconfig          |  20 ++
 drivers/power/pwrseq/Makefile         |   2 +
 drivers/power/pwrseq/core.c           | 335 ++++++++++++++++++++++++++++++++++
 drivers/power/pwrseq/pwrseq_generic.c | 224 +++++++++++++++++++++++
 include/linux/power/pwrseq.h          |  81 ++++++++
 8 files changed, 673 insertions(+)
 create mode 100644 drivers/power/pwrseq/Kconfig
 create mode 100644 drivers/power/pwrseq/Makefile
 create mode 100644 drivers/power/pwrseq/core.c
 create mode 100644 drivers/power/pwrseq/pwrseq_generic.c
 create mode 100644 include/linux/power/pwrseq.h

diff --git a/MAINTAINERS b/MAINTAINERS
index cfff2c9..ae2aa25 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9828,6 +9828,15 @@ F:	include/linux/pm_*
 F:	include/linux/powercap.h
 F:	drivers/powercap/
 
+POWER SEQUENCE LIBRARY
+M:	Peter Chen <Peter.Chen@nxp.com>
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git
+L:	linux-pm at vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/power/pwrseq/
+F:	drivers/power/pwrseq/
+F:	include/linux/power/pwrseq.h
+
 POWER SUPPLY CLASS/SUBSYSTEM and DRIVERS
 M:	Sebastian Reichel <sre@kernel.org>
 L:	linux-pm at vger.kernel.org
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 63454b5..c1bb046 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -1,3 +1,4 @@
 source "drivers/power/avs/Kconfig"
 source "drivers/power/reset/Kconfig"
 source "drivers/power/supply/Kconfig"
+source "drivers/power/pwrseq/Kconfig"
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index ff35c71..7db8035 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -1,3 +1,4 @@
 obj-$(CONFIG_POWER_AVS)		+= avs/
 obj-$(CONFIG_POWER_RESET)	+= reset/
 obj-$(CONFIG_POWER_SUPPLY)	+= supply/
+obj-$(CONFIG_POWER_SEQUENCE)	+= pwrseq/
diff --git a/drivers/power/pwrseq/Kconfig b/drivers/power/pwrseq/Kconfig
new file mode 100644
index 0000000..c6b3569
--- /dev/null
+++ b/drivers/power/pwrseq/Kconfig
@@ -0,0 +1,20 @@
+#
+# Power Sequence library
+#
+
+menuconfig POWER_SEQUENCE
+	bool "Power sequence control"
+	help
+	   It is used for drivers which needs to do power sequence
+	   (eg, turn on clock, toggle reset gpio) before the related
+	   devices can be found by hardware, eg, USB bus.
+
+if POWER_SEQUENCE
+
+config PWRSEQ_GENERIC
+	bool "Generic power sequence control"
+	depends on OF
+	help
+	   This is the generic power sequence control library, and is
+	   supposed to support common power sequence usage.
+endif
diff --git a/drivers/power/pwrseq/Makefile b/drivers/power/pwrseq/Makefile
new file mode 100644
index 0000000..ad82389
--- /dev/null
+++ b/drivers/power/pwrseq/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_POWER_SEQUENCE) += core.o
+obj-$(CONFIG_PWRSEQ_GENERIC) += pwrseq_generic.o
diff --git a/drivers/power/pwrseq/core.c b/drivers/power/pwrseq/core.c
new file mode 100644
index 0000000..3d19e62
--- /dev/null
+++ b/drivers/power/pwrseq/core.c
@@ -0,0 +1,335 @@
+/*
+ * core.c	power sequence core file
+ *
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Author: Peter Chen <peter.chen@nxp.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2  of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.
+ */
+
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/power/pwrseq.h>
+
+static DEFINE_MUTEX(pwrseq_list_mutex);
+static LIST_HEAD(pwrseq_list);
+
+static int pwrseq_get(struct device_node *np, struct pwrseq *p)
+{
+	if (p && p->get)
+		return p->get(np, p);
+
+	return -ENOTSUPP;
+}
+
+static int pwrseq_on(struct pwrseq *p)
+{
+	if (p && p->on)
+		return p->on(p);
+
+	return -ENOTSUPP;
+}
+
+static void pwrseq_off(struct pwrseq *p)
+{
+	if (p && p->off)
+		p->off(p);
+}
+
+static void pwrseq_put(struct pwrseq *p)
+{
+	if (p && p->put)
+		p->put(p);
+}
+
+/**
+ * pwrseq_register - Add pwrseq instance to global pwrseq list
+ *
+ * @pwrseq: the pwrseq instance
+ */
+void pwrseq_register(struct pwrseq *pwrseq)
+{
+	mutex_lock(&pwrseq_list_mutex);
+	list_add(&pwrseq->node, &pwrseq_list);
+	mutex_unlock(&pwrseq_list_mutex);
+}
+EXPORT_SYMBOL_GPL(pwrseq_register);
+
+/**
+ * pwrseq_unregister - Remove pwrseq instance from global pwrseq list
+ *
+ * @pwrseq: the pwrseq instance
+ */
+void pwrseq_unregister(struct pwrseq *pwrseq)
+{
+	mutex_lock(&pwrseq_list_mutex);
+	list_del(&pwrseq->node);
+	mutex_unlock(&pwrseq_list_mutex);
+}
+EXPORT_SYMBOL_GPL(pwrseq_unregister);
+
+static struct pwrseq *pwrseq_find_available_instance(struct device_node *np)
+{
+	struct pwrseq *pwrseq;
+
+	mutex_lock(&pwrseq_list_mutex);
+	list_for_each_entry(pwrseq, &pwrseq_list, node) {
+		if (pwrseq->used)
+			continue;
+
+		/* compare compatible string for pwrseq node */
+		if (of_match_node(pwrseq->pwrseq_of_match_table, np)) {
+			pwrseq->used = true;
+			mutex_unlock(&pwrseq_list_mutex);
+			return pwrseq;
+		}
+
+		/* return generic pwrseq instance */
+		if (!strcmp(pwrseq->pwrseq_of_match_table->compatible,
+				"generic")) {
+			pr_debug("using generic pwrseq instance for %s\n",
+				np->full_name);
+			pwrseq->used = true;
+			mutex_unlock(&pwrseq_list_mutex);
+			return pwrseq;
+		}
+	}
+	mutex_unlock(&pwrseq_list_mutex);
+	pr_debug("Can't find any pwrseq instances for %s\n", np->full_name);
+
+	return NULL;
+}
+
+/**
+ * of_pwrseq_on - Carry out power sequence on for device node
+ *
+ * @np: the device node would like to power on
+ *
+ * Carry out a single device power on.  If multiple devices
+ * need to be handled, use of_pwrseq_on_list() instead.
+ *
+ * Return a pointer to the power sequence instance on success,
+ * or an error code otherwise.
+ */
+struct pwrseq *of_pwrseq_on(struct device_node *np)
+{
+	struct pwrseq *pwrseq;
+	int ret;
+
+	pwrseq = pwrseq_find_available_instance(np);
+	if (!pwrseq)
+		return ERR_PTR(-ENOENT);
+
+	ret = pwrseq_get(np, pwrseq);
+	if (ret) {
+		/* Mark current pwrseq as unused */
+		pwrseq->used = false;
+		return ERR_PTR(ret);
+	}
+
+	ret = pwrseq_on(pwrseq);
+	if (ret)
+		goto pwr_put;
+
+	return pwrseq;
+
+pwr_put:
+	pwrseq_put(pwrseq);
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(of_pwrseq_on);
+
+/**
+ * of_pwrseq_off - Carry out power sequence off for this pwrseq instance
+ *
+ * @pwrseq: the pwrseq instance which related device would like to be off
+ *
+ * This API is used to power off single device, it is the opposite
+ * operation for of_pwrseq_on.
+ */
+void of_pwrseq_off(struct pwrseq *pwrseq)
+{
+	pwrseq_off(pwrseq);
+	pwrseq_put(pwrseq);
+}
+EXPORT_SYMBOL_GPL(of_pwrseq_off);
+
+/**
+ * of_pwrseq_on_list - Carry out power sequence on for list
+ *
+ * @np: the device node would like to power on
+ * @head: the list head for pwrseq list on this bus
+ *
+ * This API is used to power on multiple devices at single bus.
+ * If there are several devices on bus (eg, USB bus), uses this
+ * this API. Otherwise, use of_pwrseq_on instead. After the device
+ * is powered on successfully, it will be added to pwrseq list for
+ * this bus. The caller needs to use mutex_lock for concurrent.
+ *
+ * Return 0 on success, or an error value otherwise.
+ */
+int of_pwrseq_on_list(struct device_node *np, struct list_head *head)
+{
+	struct pwrseq *pwrseq;
+	struct pwrseq_list_per_dev *pwrseq_list_node;
+
+	pwrseq_list_node = kzalloc(sizeof(*pwrseq_list_node), GFP_KERNEL);
+	if (!pwrseq_list_node)
+		return -ENOMEM;
+
+	pwrseq = of_pwrseq_on(np);
+	if (IS_ERR(pwrseq)) {
+		kfree(pwrseq_list_node);
+		return PTR_ERR(pwrseq);
+	}
+
+	pwrseq_list_node->pwrseq = pwrseq;
+	list_add(&pwrseq_list_node->list, head);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(of_pwrseq_on_list);
+
+/**
+ * of_pwrseq_off_list - Carry out power sequence off for the list
+ *
+ * @head: the list head for pwrseq instance list on this bus
+ *
+ * This API is used to power off all devices on this bus, it is
+ * the opposite operation for of_pwrseq_on_list.
+ * The caller needs to use mutex_lock for concurrent.
+ */
+void of_pwrseq_off_list(struct list_head *head)
+{
+	struct pwrseq *pwrseq;
+	struct pwrseq_list_per_dev *pwrseq_list_node, *tmp_node;
+
+	list_for_each_entry_safe(pwrseq_list_node, tmp_node, head, list) {
+		pwrseq = pwrseq_list_node->pwrseq;
+		of_pwrseq_off(pwrseq);
+		list_del(&pwrseq_list_node->list);
+		kfree(pwrseq_list_node);
+	}
+}
+EXPORT_SYMBOL_GPL(of_pwrseq_off_list);
+
+/**
+ * pwrseq_suspend - Carry out power sequence suspend for this pwrseq instance
+ *
+ * @pwrseq: the pwrseq instance
+ *
+ * This API is used to do suspend operation on pwrseq instance.
+ *
+ * Return 0 on success, or an error value otherwise.
+ */
+int pwrseq_suspend(struct pwrseq *p)
+{
+	int ret = 0;
+
+	if (p && p->suspend)
+		ret = p->suspend(p);
+	else
+		return ret;
+
+	if (!ret)
+		p->suspended = true;
+	else
+		pr_err("%s failed\n", __func__);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(pwrseq_suspend);
+
+/**
+ * pwrseq_resume - Carry out power sequence resume for this pwrseq instance
+ *
+ * @pwrseq: the pwrseq instance
+ *
+ * This API is used to do resume operation on pwrseq instance.
+ *
+ * Return 0 on success, or an error value otherwise.
+ */
+int pwrseq_resume(struct pwrseq *p)
+{
+	int ret = 0;
+
+	if (p && p->resume)
+		ret = p->resume(p);
+	else
+		return ret;
+
+	if (!ret)
+		p->suspended = false;
+	else
+		pr_err("%s failed\n", __func__);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(pwrseq_resume);
+
+/**
+ * pwrseq_suspend_list - Carry out power sequence suspend for list
+ *
+ * @head: the list head for pwrseq instance list on this bus
+ *
+ * This API is used to do suspend on all power sequence instances on this bus.
+ * The caller needs to use mutex_lock for concurrent.
+ */
+int pwrseq_suspend_list(struct list_head *head)
+{
+	struct pwrseq *pwrseq;
+	struct pwrseq_list_per_dev *pwrseq_list_node;
+	int ret = 0;
+
+	list_for_each_entry(pwrseq_list_node, head, list) {
+		ret = pwrseq_suspend(pwrseq_list_node->pwrseq);
+		if (ret)
+			break;
+	}
+
+	if (ret) {
+		list_for_each_entry(pwrseq_list_node, head, list) {
+			pwrseq = pwrseq_list_node->pwrseq;
+			if (pwrseq->suspended)
+				pwrseq_resume(pwrseq);
+		}
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(pwrseq_suspend_list);
+
+/**
+ * pwrseq_resume_list - Carry out power sequence resume for the list
+ *
+ * @head: the list head for pwrseq instance list on this bus
+ *
+ * This API is used to do resume on all power sequence instances on this bus.
+ * The caller needs to use mutex_lock for concurrent.
+ */
+int pwrseq_resume_list(struct list_head *head)
+{
+	struct pwrseq_list_per_dev *pwrseq_list_node;
+	int ret = 0;
+
+	list_for_each_entry(pwrseq_list_node, head, list) {
+		ret = pwrseq_resume(pwrseq_list_node->pwrseq);
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(pwrseq_resume_list);
diff --git a/drivers/power/pwrseq/pwrseq_generic.c b/drivers/power/pwrseq/pwrseq_generic.c
new file mode 100644
index 0000000..0e70a38
--- /dev/null
+++ b/drivers/power/pwrseq/pwrseq_generic.c
@@ -0,0 +1,224 @@
+/*
+ * pwrseq_generic.c	Generic power sequence handling
+ *
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Author: Peter Chen <peter.chen@nxp.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2  of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+
+#include <linux/power/pwrseq.h>
+
+struct pwrseq_generic {
+	struct pwrseq pwrseq;
+	struct gpio_desc *gpiod_reset;
+	struct clk *clks[PWRSEQ_MAX_CLKS];
+	u32 duration_us;
+	bool suspended;
+};
+
+#define to_generic_pwrseq(p) container_of(p, struct pwrseq_generic, pwrseq)
+
+static int pwrseq_generic_alloc_instance(void);
+static const struct of_device_id generic_id_table[] = {
+	{ .compatible = "generic",},
+	{ /* sentinel */ }
+};
+
+static int pwrseq_generic_suspend(struct pwrseq *pwrseq)
+{
+	struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+	int clk;
+
+	for (clk = PWRSEQ_MAX_CLKS - 1; clk >= 0; clk--)
+		clk_disable_unprepare(pwrseq_gen->clks[clk]);
+
+	pwrseq_gen->suspended = true;
+	return 0;
+}
+
+static int pwrseq_generic_resume(struct pwrseq *pwrseq)
+{
+	struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+	int clk, ret = 0;
+
+	for (clk = 0; clk < PWRSEQ_MAX_CLKS && pwrseq_gen->clks[clk]; clk++) {
+		ret = clk_prepare_enable(pwrseq_gen->clks[clk]);
+		if (ret) {
+			pr_err("Can't enable clock, ret=%d\n", ret);
+			goto err_disable_clks;
+		}
+	}
+
+	pwrseq_gen->suspended = false;
+	return ret;
+
+err_disable_clks:
+	while (--clk >= 0)
+		clk_disable_unprepare(pwrseq_gen->clks[clk]);
+
+	return ret;
+}
+
+static void pwrseq_generic_put(struct pwrseq *pwrseq)
+{
+	struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+	int clk;
+
+	if (pwrseq_gen->gpiod_reset)
+		gpiod_put(pwrseq_gen->gpiod_reset);
+
+	for (clk = 0; clk < PWRSEQ_MAX_CLKS; clk++)
+		clk_put(pwrseq_gen->clks[clk]);
+
+	pwrseq_unregister(&pwrseq_gen->pwrseq);
+	kfree(pwrseq_gen);
+}
+
+static void pwrseq_generic_off(struct pwrseq *pwrseq)
+{
+	struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+	int clk;
+
+	if (pwrseq_gen->suspended)
+		return;
+
+	for (clk = PWRSEQ_MAX_CLKS - 1; clk >= 0; clk--)
+		clk_disable_unprepare(pwrseq_gen->clks[clk]);
+}
+
+static int pwrseq_generic_on(struct pwrseq *pwrseq)
+{
+	struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+	int clk, ret = 0;
+	struct gpio_desc *gpiod_reset = pwrseq_gen->gpiod_reset;
+
+	for (clk = 0; clk < PWRSEQ_MAX_CLKS && pwrseq_gen->clks[clk]; clk++) {
+		ret = clk_prepare_enable(pwrseq_gen->clks[clk]);
+		if (ret) {
+			pr_err("Can't enable clock, ret=%d\n", ret);
+			goto err_disable_clks;
+		}
+	}
+
+	if (gpiod_reset) {
+		u32 duration_us = pwrseq_gen->duration_us;
+
+		if (duration_us <= 10)
+			udelay(10);
+		else
+			usleep_range(duration_us, duration_us + 100);
+		gpiod_set_value(gpiod_reset, 0);
+	}
+
+	return ret;
+
+err_disable_clks:
+	while (--clk >= 0)
+		clk_disable_unprepare(pwrseq_gen->clks[clk]);
+
+	return ret;
+}
+
+static int pwrseq_generic_get(struct device_node *np, struct pwrseq *pwrseq)
+{
+	struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+	enum of_gpio_flags flags;
+	int reset_gpio, clk, ret = 0;
+
+	for (clk = 0; clk < PWRSEQ_MAX_CLKS; clk++) {
+		pwrseq_gen->clks[clk] = of_clk_get(np, clk);
+		if (IS_ERR(pwrseq_gen->clks[clk])) {
+			ret = PTR_ERR(pwrseq_gen->clks[clk]);
+			if (ret != -ENOENT)
+				goto err_put_clks;
+			pwrseq_gen->clks[clk] = NULL;
+			break;
+		}
+	}
+
+	reset_gpio = of_get_named_gpio_flags(np, "reset-gpios", 0, &flags);
+	if (gpio_is_valid(reset_gpio)) {
+		unsigned long gpio_flags;
+
+		if (flags & OF_GPIO_ACTIVE_LOW)
+			gpio_flags = GPIOF_ACTIVE_LOW | GPIOF_OUT_INIT_LOW;
+		else
+			gpio_flags = GPIOF_OUT_INIT_HIGH;
+
+		ret = gpio_request_one(reset_gpio, gpio_flags,
+				"pwrseq-reset-gpios");
+		if (ret)
+			goto err_put_clks;
+
+		pwrseq_gen->gpiod_reset = gpio_to_desc(reset_gpio);
+		of_property_read_u32(np, "reset-duration-us",
+				&pwrseq_gen->duration_us);
+	} else if (reset_gpio == -ENOENT) {
+		; /* no such gpio */
+	} else {
+		ret = reset_gpio;
+		pr_err("Failed to get reset gpio on %s, err = %d\n",
+				np->full_name, reset_gpio);
+		goto err_put_clks;
+	}
+
+	/* allocate new one for later pwrseq instance request */
+	ret = pwrseq_generic_alloc_instance();
+	if (ret)
+		goto err_put_gpio;
+
+	return 0;
+
+err_put_gpio:
+	if (pwrseq_gen->gpiod_reset)
+		gpiod_put(pwrseq_gen->gpiod_reset);
+err_put_clks:
+	while (--clk >= 0)
+		clk_put(pwrseq_gen->clks[clk]);
+	return ret;
+}
+
+static int pwrseq_generic_alloc_instance(void)
+{
+	struct pwrseq_generic *pwrseq_gen;
+
+	pwrseq_gen = kzalloc(sizeof(*pwrseq_gen), GFP_KERNEL);
+	if (!pwrseq_gen)
+		return -ENOMEM;
+
+	pwrseq_gen->pwrseq.pwrseq_of_match_table = generic_id_table;
+	pwrseq_gen->pwrseq.get = pwrseq_generic_get;
+	pwrseq_gen->pwrseq.on = pwrseq_generic_on;
+	pwrseq_gen->pwrseq.off = pwrseq_generic_off;
+	pwrseq_gen->pwrseq.put = pwrseq_generic_put;
+	pwrseq_gen->pwrseq.suspend = pwrseq_generic_suspend;
+	pwrseq_gen->pwrseq.resume = pwrseq_generic_resume;
+
+	pwrseq_register(&pwrseq_gen->pwrseq);
+	return 0;
+}
+
+static int __init pwrseq_generic_register(void)
+{
+	return pwrseq_generic_alloc_instance();
+}
+postcore_initcall(pwrseq_generic_register)
diff --git a/include/linux/power/pwrseq.h b/include/linux/power/pwrseq.h
new file mode 100644
index 0000000..cbc344c
--- /dev/null
+++ b/include/linux/power/pwrseq.h
@@ -0,0 +1,81 @@
+#ifndef __LINUX_PWRSEQ_H
+#define __LINUX_PWRSEQ_H
+
+#include <linux/of.h>
+
+#define PWRSEQ_MAX_CLKS		3
+
+/**
+ * struct pwrseq - the power sequence structure
+ * @pwrseq_of_match_table: the OF device id table this pwrseq library supports
+ * @node: the list pointer to be added to pwrseq list
+ * @get: the API is used to get pwrseq instance from the device node
+ * @on: do power on for this pwrseq instance
+ * @off: do power off for this pwrseq instance
+ * @put: release the resources on this pwrseq instance
+ * @suspend: do suspend operation on this pwrseq instance
+ * @resume: do resume operation on this pwrseq instance
+ * @used: this pwrseq instance is used by device
+ */
+struct pwrseq {
+	const struct of_device_id *pwrseq_of_match_table;
+	struct list_head node;
+	int (*get)(struct device_node *np, struct pwrseq *p);
+	int (*on)(struct pwrseq *p);
+	void (*off)(struct pwrseq *p);
+	void (*put)(struct pwrseq *p);
+	int (*suspend)(struct pwrseq *p);
+	int (*resume)(struct pwrseq *p);
+	bool used;
+	bool suspended;
+};
+
+/* used for power sequence instance list in one driver */
+struct pwrseq_list_per_dev {
+	struct pwrseq *pwrseq;
+	struct list_head list;
+};
+
+#if IS_ENABLED(CONFIG_POWER_SEQUENCE)
+void pwrseq_register(struct pwrseq *pwrseq);
+void pwrseq_unregister(struct pwrseq *pwrseq);
+struct pwrseq *of_pwrseq_on(struct device_node *np);
+void of_pwrseq_off(struct pwrseq *pwrseq);
+int of_pwrseq_on_list(struct device_node *np, struct list_head *head);
+void of_pwrseq_off_list(struct list_head *head);
+int pwrseq_suspend(struct pwrseq *p);
+int pwrseq_resume(struct pwrseq *p);
+int pwrseq_suspend_list(struct list_head *head);
+int pwrseq_resume_list(struct list_head *head);
+#else
+static inline void pwrseq_register(struct pwrseq *pwrseq) {}
+static inline void pwrseq_unregister(struct pwrseq *pwrseq) {}
+static inline struct pwrseq *of_pwrseq_on(struct device_node *np)
+{
+	return NULL;
+}
+static void of_pwrseq_off(struct pwrseq *pwrseq) {}
+static int of_pwrseq_on_list(struct device_node *np, struct list_head *head)
+{
+	return 0;
+}
+static void of_pwrseq_off_list(struct list_head *head) {}
+static int pwrseq_suspend(struct pwrseq *p)
+{
+	return 0;
+}
+static int pwrseq_resume(struct pwrseq *p)
+{
+	return 0;
+}
+static int pwrseq_suspend_list(struct list_head *head)
+{
+	return 0;
+}
+static int pwrseq_resume_list(struct list_head *head)
+{
+	return 0;
+}
+#endif /* CONFIG_POWER_SEQUENCE */
+
+#endif  /* __LINUX_PWRSEQ_H */
-- 
2.7.4

^ permalink raw reply related

* [PATCH v11 3/8] binding-doc: usb: usb-device: add optional properties for power sequence
From: Peter Chen @ 2017-01-05  6:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>

Add optional properties for power sequence.

Signed-off-by: Peter Chen <peter.chen@nxp.com>
Acked-by: Rob Herring <robh@kernel.org>
---
 Documentation/devicetree/bindings/usb/usb-device.txt | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/usb/usb-device.txt b/Documentation/devicetree/bindings/usb/usb-device.txt
index 1c35e7b..3661dd2 100644
--- a/Documentation/devicetree/bindings/usb/usb-device.txt
+++ b/Documentation/devicetree/bindings/usb/usb-device.txt
@@ -13,6 +13,10 @@ Required properties:
 - reg: the port number which this device is connecting to, the range
   is 1-31.
 
+Optional properties:
+power sequence properties, see
+Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt for detail
+
 Example:
 
 &usb1 {
@@ -21,8 +25,12 @@ Example:
 	#address-cells = <1>;
 	#size-cells = <0>;
 
-	hub: genesys at 1 {
+	genesys: hub at 1 {
 		compatible = "usb5e3,608";
 		reg = <1>;
+
+		clocks = <&clks IMX6SX_CLK_CKO>;
+		reset-gpios = <&gpio4 5 GPIO_ACTIVE_LOW>; /* hub reset pin */
+		reset-duration-us = <10>;
 	};
 }
-- 
2.7.4

^ permalink raw reply related

* [PATCH v11 4/8] usb: core: add power sequence handling for USB devices
From: Peter Chen @ 2017-01-05  6:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>

Some hard-wired USB devices need to do power sequence to let the
device work normally, the typical power sequence like: enable USB
PHY clock, toggle reset pin, etc. But current Linux USB driver
lacks of such code to do it, it may cause some hard-wired USB devices
works abnormal or can't be recognized by controller at all.

In this patch, it calls power sequence library APIs to finish
the power sequence events. It will do power on sequence at hub's
probe for all devices under this hub (includes root hub).
At hub_disconnect, it will do power off sequence which is at powered
on list.

Signed-off-by: Peter Chen <peter.chen@nxp.com>
Tested-by Joshua Clayton <stillcompiling@gmail.com>
Tested-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
Reviewed-by: Vaibhav Hiremath <hvaibhav.linux@gmail.com>
---
 drivers/usb/Kconfig    |  1 +
 drivers/usb/core/hub.c | 48 ++++++++++++++++++++++++++++++++++++++++++++----
 drivers/usb/core/hub.h |  1 +
 3 files changed, 46 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index fbe493d..706f261 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -40,6 +40,7 @@ config USB
 	tristate "Support for Host-side USB"
 	depends on USB_ARCH_HAS_HCD
 	select USB_COMMON
+	select POWER_SEQUENCE
 	select NLS  # for UTF-8 strings
 	---help---
 	  Universal Serial Bus (USB) is a specification for a serial bus
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 1fa5c0f..cec2fad 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -28,6 +28,7 @@
 #include <linux/mutex.h>
 #include <linux/random.h>
 #include <linux/pm_qos.h>
+#include <linux/power/pwrseq.h>
 
 #include <linux/uaccess.h>
 #include <asm/byteorder.h>
@@ -1645,6 +1646,7 @@ static void hub_disconnect(struct usb_interface *intf)
 	hub->error = 0;
 	hub_quiesce(hub, HUB_DISCONNECT);
 
+	of_pwrseq_off_list(&hub->pwrseq_on_list);
 	mutex_lock(&usb_port_peer_mutex);
 
 	/* Avoid races with recursively_mark_NOTATTACHED() */
@@ -1672,12 +1674,41 @@ static void hub_disconnect(struct usb_interface *intf)
 	kref_put(&hub->kref, hub_release);
 }
 
+#ifdef CONFIG_OF
+static int hub_of_pwrseq_on(struct usb_hub *hub)
+{
+	struct device *parent;
+	struct usb_device *hdev = hub->hdev;
+	struct device_node *np;
+	int ret;
+
+	if (hdev->parent)
+		parent = &hdev->dev;
+	else
+		parent = bus_to_hcd(hdev->bus)->self.controller;
+
+	for_each_child_of_node(parent->of_node, np) {
+		ret = of_pwrseq_on_list(np, &hub->pwrseq_on_list);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+#else
+static int hub_of_pwrseq_on(struct usb_hub *hub)
+{
+	return 0;
+}
+#endif
+
 static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
 {
 	struct usb_host_interface *desc;
 	struct usb_endpoint_descriptor *endpoint;
 	struct usb_device *hdev;
 	struct usb_hub *hub;
+	int ret = -ENODEV;
 
 	desc = intf->cur_altsetting;
 	hdev = interface_to_usbdev(intf);
@@ -1782,6 +1813,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
 	INIT_DELAYED_WORK(&hub->leds, led_work);
 	INIT_DELAYED_WORK(&hub->init_work, NULL);
 	INIT_WORK(&hub->events, hub_event);
+	INIT_LIST_HEAD(&hub->pwrseq_on_list);
 	usb_get_intf(intf);
 	usb_get_dev(hdev);
 
@@ -1795,11 +1827,14 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
 	if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND)
 		hub->quirk_check_port_auto_suspend = 1;
 
-	if (hub_configure(hub, endpoint) >= 0)
-		return 0;
+	if (hub_configure(hub, endpoint) >= 0) {
+		ret = hub_of_pwrseq_on(hub);
+		if (!ret)
+			return 0;
+	}
 
 	hub_disconnect(intf);
-	return -ENODEV;
+	return ret;
 }
 
 static int
@@ -3613,14 +3648,19 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
 
 	/* stop hub_wq and related activity */
 	hub_quiesce(hub, HUB_SUSPEND);
-	return 0;
+	return pwrseq_suspend_list(&hub->pwrseq_on_list);
 }
 
 static int hub_resume(struct usb_interface *intf)
 {
 	struct usb_hub *hub = usb_get_intfdata(intf);
+	int ret;
 
 	dev_dbg(&intf->dev, "%s\n", __func__);
+	ret = pwrseq_resume_list(&hub->pwrseq_on_list);
+	if (ret)
+		return ret;
+
 	hub_activate(hub, HUB_RESUME);
 	return 0;
 }
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 34c1a7e..cd86f91 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -78,6 +78,7 @@ struct usb_hub {
 	struct delayed_work	init_work;
 	struct work_struct      events;
 	struct usb_port		**ports;
+	struct list_head	pwrseq_on_list; /* powered pwrseq node list */
 };
 
 /**
-- 
2.7.4

^ permalink raw reply related

* [PATCH v11 5/8] usb: chipidea: let chipidea core device of_node equal's glue layer device of_node
From: Peter Chen @ 2017-01-05  6:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>

From: Peter Chen <peter.chen@freescale.com>

At device tree, we have no device node for chipidea core,
the glue layer's node is the parent node for host and udc
device. But in related driver, the parent device is chipidea
core. So, in order to let the common driver get parent's node,
we let the core's device node equals glue layer device node.

Signed-off-by: Peter Chen <peter.chen@freescale.com>
Tested-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
Tested-by Joshua Clayton <stillcompiling@gmail.com>
---
 drivers/usb/chipidea/core.c | 27 ++++++++++++++++++++++-----
 1 file changed, 22 insertions(+), 5 deletions(-)

diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 3dbb4a2..fdffc67 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -928,6 +928,16 @@ static int ci_hdrc_probe(struct platform_device *pdev)
 		return -ENODEV;
 	}
 
+	/*
+	 * At device tree, we have no device node for chipidea core,
+	 * the glue layer's node is the parent node for host and udc
+	 * device. But in related driver, the parent device is chipidea
+	 * core. So, in order to let the common driver get parent's node,
+	 * we let the core's device node equals glue layer's node.
+	 */
+	if (dev->parent && dev->parent->of_node)
+		dev->of_node = dev->parent->of_node;
+
 	if (ci->platdata->phy) {
 		ci->phy = ci->platdata->phy;
 	} else if (ci->platdata->usb_phy) {
@@ -938,11 +948,15 @@ static int ci_hdrc_probe(struct platform_device *pdev)
 
 		/* if both generic PHY and USB PHY layers aren't enabled */
 		if (PTR_ERR(ci->phy) == -ENOSYS &&
-				PTR_ERR(ci->usb_phy) == -ENXIO)
-			return -ENXIO;
+				PTR_ERR(ci->usb_phy) == -ENXIO) {
+			ret = -ENXIO;
+			goto clear_of_node;
+		}
 
-		if (IS_ERR(ci->phy) && IS_ERR(ci->usb_phy))
-			return -EPROBE_DEFER;
+		if (IS_ERR(ci->phy) && IS_ERR(ci->usb_phy)) {
+			ret = -EPROBE_DEFER;
+			goto clear_of_node;
+		}
 
 		if (IS_ERR(ci->phy))
 			ci->phy = NULL;
@@ -953,7 +967,7 @@ static int ci_hdrc_probe(struct platform_device *pdev)
 	ret = ci_usb_phy_init(ci);
 	if (ret) {
 		dev_err(dev, "unable to init phy: %d\n", ret);
-		return ret;
+		goto clear_of_node;
 	}
 
 	ci->hw_bank.phys = res->start;
@@ -1059,6 +1073,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
 	ci_role_destroy(ci);
 deinit_phy:
 	ci_usb_phy_exit(ci);
+clear_of_node:
+	dev->of_node = NULL;
 
 	return ret;
 }
@@ -1077,6 +1093,7 @@ static int ci_hdrc_remove(struct platform_device *pdev)
 	ci_extcon_unregister(ci);
 	ci_role_destroy(ci);
 	ci_hdrc_enter_lpm(ci, true);
+	ci->dev->of_node = NULL;
 	ci_usb_phy_exit(ci);
 
 	return 0;
-- 
2.7.4

^ permalink raw reply related

* [PATCH v11 6/8] ARM: dts: imx6qdl: Enable usb node children with <reg>
From: Peter Chen @ 2017-01-05  6:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>

From: Joshua Clayton <stillcompiling@gmail.com>

Give usb nodes #address and #size attributes, so that a child node
representing a permanently connected device such as an onboard hub may
be addressed with a <reg> attribute

Signed-off-by: Joshua Clayton <stillcompiling@gmail.com>
Signed-off-by: Peter Chen <peter.chen@nxp.com>
---
 arch/arm/boot/dts/imx6qdl.dtsi | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm/boot/dts/imx6qdl.dtsi b/arch/arm/boot/dts/imx6qdl.dtsi
index 53e6e63..e6725f1 100644
--- a/arch/arm/boot/dts/imx6qdl.dtsi
+++ b/arch/arm/boot/dts/imx6qdl.dtsi
@@ -936,6 +936,8 @@
 
 			usbh1: usb at 02184200 {
 				compatible = "fsl,imx6q-usb", "fsl,imx27-usb";
+				#address-cells = <1>;
+				#size-cells = <0>;
 				reg = <0x02184200 0x200>;
 				interrupts = <0 40 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&clks IMX6QDL_CLK_USBOH3>;
@@ -950,6 +952,8 @@
 
 			usbh2: usb at 02184400 {
 				compatible = "fsl,imx6q-usb", "fsl,imx27-usb";
+				#address-cells = <1>;
+				#size-cells = <0>;
 				reg = <0x02184400 0x200>;
 				interrupts = <0 41 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&clks IMX6QDL_CLK_USBOH3>;
@@ -963,6 +967,8 @@
 
 			usbh3: usb at 02184600 {
 				compatible = "fsl,imx6q-usb", "fsl,imx27-usb";
+				#address-cells = <1>;
+				#size-cells = <0>;
 				reg = <0x02184600 0x200>;
 				interrupts = <0 42 IRQ_TYPE_LEVEL_HIGH>;
 				clocks = <&clks IMX6QDL_CLK_USBOH3>;
-- 
2.7.4

^ permalink raw reply related

* [PATCH v11 7/8] ARM: dts: imx6qdl-udoo.dtsi: fix onboard USB HUB property
From: Peter Chen @ 2017-01-05  6:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>

The current dts describes USB HUB's property at USB controller's
entry, it is improper. The USB HUB should be the child node
under USB controller, and power sequence properties are under
it. Besides, using gpio pinctrl setting for USB2415's reset pin.

Signed-off-by: Peter Chen <peter.chen@nxp.com>
Signed-off-by: Joshua Clayton <stillcompiling@gmail.com>
Tested-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
 arch/arm/boot/dts/imx6qdl-udoo.dtsi | 26 ++++++++++++--------------
 1 file changed, 12 insertions(+), 14 deletions(-)

diff --git a/arch/arm/boot/dts/imx6qdl-udoo.dtsi b/arch/arm/boot/dts/imx6qdl-udoo.dtsi
index c96c91d..a173de2 100644
--- a/arch/arm/boot/dts/imx6qdl-udoo.dtsi
+++ b/arch/arm/boot/dts/imx6qdl-udoo.dtsi
@@ -9,6 +9,8 @@
  *
  */
 
+#include <dt-bindings/gpio/gpio.h>
+
 / {
 	aliases {
 		backlight = &backlight;
@@ -58,17 +60,6 @@
 		#address-cells = <1>;
 		#size-cells = <0>;
 
-		reg_usb_h1_vbus: regulator at 0 {
-			compatible = "regulator-fixed";
-			reg = <0>;
-			regulator-name = "usb_h1_vbus";
-			regulator-min-microvolt = <5000000>;
-			regulator-max-microvolt = <5000000>;
-			enable-active-high;
-			startup-delay-us = <2>; /* USB2415 requires a POR of 1 us minimum */
-			gpio = <&gpio7 12 0>;
-		};
-
 		reg_panel: regulator at 1 {
 			compatible = "regulator-fixed";
 			reg = <1>;
@@ -188,7 +179,7 @@
 
 		pinctrl_usbh: usbhgrp {
 			fsl,pins = <
-				MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x80000000
+				MX6QDL_PAD_GPIO_17__GPIO7_IO12	0x1b0b0
 				MX6QDL_PAD_NANDF_CS2__CCM_CLKO2 0x130b0
 			>;
 		};
@@ -259,9 +250,16 @@
 &usbh1 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_usbh>;
-	vbus-supply = <&reg_usb_h1_vbus>;
-	clocks = <&clks IMX6QDL_CLK_CKO>;
 	status = "okay";
+
+	usb2415: hub at 1 {
+		compatible = "usb424,2514";
+		reg = <1>;
+
+		clocks = <&clks IMX6QDL_CLK_CKO>;
+		reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>;
+		reset-duration-us = <3000>;
+	};
 };
 
 &usdhc3 {
-- 
2.7.4

^ permalink raw reply related

* [PATCH v11 8/8] ARM: dts: imx6q-evi: Fix onboard hub reset line
From: Peter Chen @ 2017-01-05  6:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>

From: Joshua Clayton <stillcompiling@gmail.com>

Previously the onboard hub was made to work by treating its
reset gpio as a regulator enable.
Get rid of that kludge now that pwseq has added reset gpio support
Move pin muxing the hub reset pin into the usbh1 group

Signed-off-by: Joshua Clayton <stillcompiling@gmail.com>
Signed-off-by: Peter Chen <peter.chen@nxp.com>
---
 arch/arm/boot/dts/imx6q-evi.dts | 25 +++++++------------------
 1 file changed, 7 insertions(+), 18 deletions(-)

diff --git a/arch/arm/boot/dts/imx6q-evi.dts b/arch/arm/boot/dts/imx6q-evi.dts
index 7c7c1a8..79a0bd5 100644
--- a/arch/arm/boot/dts/imx6q-evi.dts
+++ b/arch/arm/boot/dts/imx6q-evi.dts
@@ -54,18 +54,6 @@
 		reg = <0x10000000 0x40000000>;
 	};
 
-	reg_usbh1_vbus: regulator-usbhubreset {
-		compatible = "regulator-fixed";
-		regulator-name = "usbh1_vbus";
-		regulator-min-microvolt = <5000000>;
-		regulator-max-microvolt = <5000000>;
-		enable-active-high;
-		startup-delay-us = <2>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_usbh1_hubreset>;
-		gpio = <&gpio7 12 GPIO_ACTIVE_HIGH>;
-	};
-
 	reg_usb_otg_vbus: regulator-usbotgvbus {
 		compatible = "regulator-fixed";
 		regulator-name = "usb_otg_vbus";
@@ -207,12 +195,18 @@
 };
 
 &usbh1 {
-	vbus-supply = <&reg_usbh1_vbus>;
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_usbh1>;
 	dr_mode = "host";
 	disable-over-current;
 	status = "okay";
+
+	usb2415host: hub at 1 {
+		compatible = "usb424,2513";
+		reg = <1>;
+		reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>;
+		reset-duration-us = <3000>;
+	};
 };
 
 &usbotg {
@@ -468,11 +462,6 @@
 			MX6QDL_PAD_GPIO_3__USB_H1_OC 0x1b0b0
 			/* usbh1_b OC */
 			MX6QDL_PAD_GPIO_0__GPIO1_IO00 0x1b0b0
-		>;
-	};
-
-	pinctrl_usbh1_hubreset: usbh1hubresetgrp {
-		fsl,pins = <
 			MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x1b0b0
 		>;
 	};
-- 
2.7.4

^ permalink raw reply related

* [PATCH 16/22] mfd: axp20x: add V_OFF to writeable regs for AXP20X and AXP22X
From: Chen-Yu Tsai @ 2017-01-05  6:02 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170104115731.GF24058@dell>

On Wed, Jan 4, 2017 at 7:57 PM, Lee Jones <lee.jones@linaro.org> wrote:
> On Mon, 02 Jan 2017, Quentin Schulz wrote:
>
>> The V_OFF register has its first 3 read-write bits for the minimal
>> voltage (Voff) of the battery before the system is automatically shut
>> down due to the power being too low.
>>
>> This adds V_OFF register to the writeable registers of AXP20X and AXP22X
>> PMICs.
>>
>> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>> ---
>>  drivers/mfd/axp20x.c | 4 ++--
>>  1 file changed, 2 insertions(+), 2 deletions(-)
>
> For my own reference:
>   Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>

Acked-by: Chen-Yu Tsai <wens@csie.org>

>
>> diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
>> index 19bdba3..7f0f05f 100644
>> --- a/drivers/mfd/axp20x.c
>> +++ b/drivers/mfd/axp20x.c
>> @@ -65,7 +65,7 @@ static const struct regmap_access_table axp152_volatile_table = {
>>
>>  static const struct regmap_range axp20x_writeable_ranges[] = {
>>       regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ5_STATE),
>> -     regmap_reg_range(AXP20X_VBUS_IPSOUT_MGMT, AXP20X_VBUS_IPSOUT_MGMT),
>> +     regmap_reg_range(AXP20X_VBUS_IPSOUT_MGMT, AXP20X_V_OFF),
>>       regmap_reg_range(AXP20X_CHRG_CTRL1, AXP20X_CHRG_CTRL1),
>>       regmap_reg_range(AXP20X_DCDC_MODE, AXP20X_FG_RES),
>>       regmap_reg_range(AXP20X_RDC_H, AXP20X_OCV(AXP20X_OCV_MAX)),
>> @@ -94,7 +94,7 @@ static const struct regmap_access_table axp20x_volatile_table = {
>>  /* AXP22x ranges are shared with the AXP809, as they cover the same range */
>>  static const struct regmap_range axp22x_writeable_ranges[] = {
>>       regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ5_STATE),
>> -     regmap_reg_range(AXP20X_VBUS_IPSOUT_MGMT, AXP20X_VBUS_IPSOUT_MGMT),
>> +     regmap_reg_range(AXP20X_VBUS_IPSOUT_MGMT, AXP20X_V_OFF),
>>       regmap_reg_range(AXP20X_CHRG_CTRL1, AXP20X_CHRG_CTRL1),
>>       regmap_reg_range(AXP20X_DCDC_MODE, AXP22X_BATLOW_THRES1),
>>  };
>
> --
> Lee Jones
> Linaro STMicroelectronics Landing Team Lead
> Linaro.org ? Open source software for ARM SoCs
> Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply

* [PATCH v6 03/14] ACPI: ARM64: IORT: add missing comment for iort_dev_find_its_id()
From: Hanjun Guo @ 2017-01-05  6:05 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170104143442.GA8604@red-moon>

On 2017/1/4 22:34, Lorenzo Pieralisi wrote:
> On Mon, Jan 02, 2017 at 09:31:34PM +0800, Hanjun Guo wrote:
>> We are missing req_id's comment for iort_dev_find_its_id(),
>> add it back.
>
> "Add missing req_id parameter to the iort_dev_find_its_id() function
> kernel-doc comment."
>
>> Signed-off-by: Hanjun Guo <hanjun.guo@linaro.org>
>> Tested-by: Majun <majun258@huawei.com>
>> Tested-by: Xinwei Kong <kong.kongxinwei@hisilicon.com>
>> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
>> Cc: Tomasz Nowicki <tn@semihalf.com>
>> ---
>>  drivers/acpi/arm64/iort.c | 1 +
>>  1 file changed, 1 insertion(+)
>>
>> diff --git a/drivers/acpi/arm64/iort.c b/drivers/acpi/arm64/iort.c
>> index 46e2d82..174e983 100644
>> --- a/drivers/acpi/arm64/iort.c
>> +++ b/drivers/acpi/arm64/iort.c
>> @@ -446,6 +446,7 @@ u32 iort_msi_map_rid(struct device *dev, u32 req_id)
>>  /**
>>   * iort_dev_find_its_id() - Find the ITS identifier for a device
>>   * @dev: The device.
>> + * @req_id: Device's Requster ID
>
> s/Requster/Requester
>
> We can send it upstream independently along with some other patches
> in this series but I will have a look at the whole series first.

Do you mean go to 4.10-rcx?

Thanks
Hanjun

^ permalink raw reply

* [PATCH 15/22] mfd: axp20x: add CHRG_CTRL1 to writeable regs for AXP20X/AXP22X
From: Chen-Yu Tsai @ 2017-01-05  6:10 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170102163723.7939-16-quentin.schulz@free-electrons.com>

On Tue, Jan 3, 2017 at 12:37 AM, Quentin Schulz
<quentin.schulz@free-electrons.com> wrote:
> The CHR_CTRL1 register is made of 7 read-write bits with one being used
> to set the target voltage for battery charging.

The description is incorrect.

All 8 bits are read-write:

  - The highest bit enables the charger module
  - Bits [6:5] set the target voltage
  - Bits [4:3] set when the charge cycle ends, based on percentage
    of charge current
  - Bits [2:0] set the charge current

Feel free to use the above in the commit message.

>
> This adds the CHRG_CTRL1 register to the list of writeable registers for
> AXP20X and AXP22X PMICs.

You might want to add up to CHRG_CTRL3 for the AXP22x and CHRG_CTRL2
for the AXP20x. These control additional aspects of the charger.

AXP20X_CHRG_BAK_CTRL controls the charger for the RTC battery. You
could add this now, or let the person doing the RTC battery driver
add it.

Regards
ChenYu

>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> ---
>  drivers/mfd/axp20x.c | 2 ++
>  1 file changed, 2 insertions(+)
>
> diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
> index 65c57d0..19bdba3 100644
> --- a/drivers/mfd/axp20x.c
> +++ b/drivers/mfd/axp20x.c
> @@ -66,6 +66,7 @@ static const struct regmap_access_table axp152_volatile_table = {
>  static const struct regmap_range axp20x_writeable_ranges[] = {
>         regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ5_STATE),
>         regmap_reg_range(AXP20X_VBUS_IPSOUT_MGMT, AXP20X_VBUS_IPSOUT_MGMT),
> +       regmap_reg_range(AXP20X_CHRG_CTRL1, AXP20X_CHRG_CTRL1),
>         regmap_reg_range(AXP20X_DCDC_MODE, AXP20X_FG_RES),
>         regmap_reg_range(AXP20X_RDC_H, AXP20X_OCV(AXP20X_OCV_MAX)),
>  };
> @@ -94,6 +95,7 @@ static const struct regmap_access_table axp20x_volatile_table = {
>  static const struct regmap_range axp22x_writeable_ranges[] = {
>         regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ5_STATE),
>         regmap_reg_range(AXP20X_VBUS_IPSOUT_MGMT, AXP20X_VBUS_IPSOUT_MGMT),
> +       regmap_reg_range(AXP20X_CHRG_CTRL1, AXP20X_CHRG_CTRL1),
>         regmap_reg_range(AXP20X_DCDC_MODE, AXP22X_BATLOW_THRES1),
>  };
>
> --
> 2.9.3
>

^ permalink raw reply

* [PATCH 07/22] dt-bindings: power: supply: add AXP20X/AXP22X AC power supply
From: Chen-Yu Tsai @ 2017-01-05  6:17 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20170104131410.mni7opxnouzagypu@rob-hp-laptop>

Hi Quentin,

On Wed, Jan 4, 2017 at 9:14 PM, Rob Herring <robh@kernel.org> wrote:
> On Mon, Jan 02, 2017 at 05:37:07PM +0100, Quentin Schulz wrote:
>> The X-Powers AXP20X and AXP22X PMICs have an AC entry to supply power to
>> the board. They have a few registers dedicated to the status of the AC
>> power supply.
>>
>> This adds the DT binding documentation for the AC power supply for
>> AXP20X and AXP22X PMICs.
>>
>> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>> ---
>>  .../bindings/power/supply/axp20x_ac_power.txt      | 28 ++++++++++++++++++++++
>>  1 file changed, 28 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/power/supply/axp20x_ac_power.txt
>>
>> diff --git a/Documentation/devicetree/bindings/power/supply/axp20x_ac_power.txt b/Documentation/devicetree/bindings/power/supply/axp20x_ac_power.txt
>> new file mode 100644
>> index 0000000..16d0de4
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/power/supply/axp20x_ac_power.txt
>> @@ -0,0 +1,28 @@
>> +AXP20X and AXP22X PMICs' AC power supply
>> +
>> +Required Properties:
>> + - compatible: One of:
>> +                     "x-powers,axp202-ac-power-supply"
>> +                     "x-powers,axp221-ac-power-supply"
>> +
>> +More Required Properties for AXP20X PMICs:
>> + - io-channels: phandles to ACIN voltage and current ADC channels
>> + - io-channel-names = "acin_v", "acin_i";
>> +
>> +This node is a subnode of the axp20x PMIC.
>> +
>> +The AXP20X can read the current current and voltage supplied by AC by
>> +reading ADC channels from the AXP20X ADC.
>> +
>> +The AXP22X is only able to tell if an AC power supply is present and
>> +usable.
>> +
>> +Example:
>> +
>> +&axp209 {
>> +     ac_power_supply: ac_power_supply {
>
> power-supply {
>
>> +             compatible = "x-powers,axp202-ac-power-supply";
>> +             io-channels = <&axp209_adc 0>, <&axp209_adc 1>;
>
> Is this assignment fixed? If so, then it doesn't need to be in DT.

Is there any case that we actually need to use the IIO channels
from the device tree? Seems to me its limited to the other AXP
sub-devices.

If so you could use struct iio_map to map the channels to other
devices by name. See axp288_adc for an example.

Regards
ChenYu

>> +             io-channel-names = "acin_v", "acin_i";
>> +     };
>> +};
>> --
>> 2.9.3
>>

^ permalink raw reply

* [PATCH v5 13/17] irqdomain: irq_domain_check_msi_remap
From: kbuild test robot @ 2017-01-05  6:28 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483536746-2725-14-git-send-email-eric.auger@redhat.com>

Hi Eric,

[auto build test ERROR on linus/master]
[also build test ERROR on v4.10-rc2 next-20170105]
[cannot apply to iommu/next]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Eric-Auger/KVM-PCIe-MSI-passthrough-on-ARM-ARM64-and-IOVA-reserved-regions/20170105-132853
config: i386-randconfig-x003-201701 (attached as .config)
compiler: gcc-6 (Debian 6.2.0-3) 6.2.0 20160901
reproduce:
        # save the attached .config to linux build tree
        make ARCH=i386 

All errors (new ones prefixed by >>):

   kernel/irq/irqdomain.c: In function 'irq_domain_is_msi_remap':
>> kernel/irq/irqdomain.c:289:17: error: 'struct irq_domain' has no member named 'parent'
     for (; h; h = h->parent) {
                    ^~

vim +289 kernel/irq/irqdomain.c

   283	 * @domain: domain pointer
   284	 */
   285	static bool irq_domain_is_msi_remap(struct irq_domain *domain)
   286	{
   287		struct irq_domain *h = domain;
   288	
 > 289		for (; h; h = h->parent) {
   290			if (h->flags & IRQ_DOMAIN_FLAG_MSI_REMAP)
   291				return true;
   292		}

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
-------------- next part --------------
A non-text attachment was scrubbed...
Name: .config.gz
Type: application/gzip
Size: 25534 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20170105/89886e81/attachment-0001.gz>

^ permalink raw reply

* [PATCH v2 2/2] phy: sun4i-usb: Replace the deprecated extcon API
From: Chen-Yu Tsai @ 2017-01-05  6:42 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483071089-8362-3-git-send-email-cw00.choi@samsung.com>

On Fri, Dec 30, 2016 at 12:11 PM, Chanwoo Choi <cw00.choi@samsung.com> wrote:
> This patch replaces the deprecated extcon API as following:
> - extcon_set_cable_state_() -> extcon_set_state_sync()
>
> Cc: Kishon Vijay Abraham I <kishon@ti.com>
> Cc: Maxime Ripard <maxime.ripard@free-electrons.com>
> Cc: Chen-Yu Tsai <wens@csie.org>
> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

> ---
>  drivers/phy/phy-sun4i-usb.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c
> index bf28a0fdd569..eae171e18043 100644
> --- a/drivers/phy/phy-sun4i-usb.c
> +++ b/drivers/phy/phy-sun4i-usb.c
> @@ -534,7 +534,7 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work)
>         mutex_unlock(&phy0->mutex);
>
>         if (id_notify) {
> -               extcon_set_cable_state_(data->extcon, EXTCON_USB_HOST,
> +               extcon_set_state_sync(data->extcon, EXTCON_USB_HOST,
>                                         !id_det);
>                 /* When leaving host mode force end the session here */
>                 if (force_session_end && id_det == 1) {
> @@ -547,7 +547,7 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work)
>         }
>
>         if (vbus_notify)
> -               extcon_set_cable_state_(data->extcon, EXTCON_USB, vbus_det);
> +               extcon_set_state_sync(data->extcon, EXTCON_USB, vbus_det);
>
>         if (sun4i_usb_phy0_poll(data))
>                 queue_delayed_work(system_wq, &data->detect, POLL_TIME);
> --
> 1.9.1
>

^ permalink raw reply

* [PATCH v6 1/5] dt-bindings: zte: documents zx2967 power domain driver bindings
From: Shawn Guo @ 2017-01-05  6:46 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483530494-14177-1-git-send-email-baoyou.xie@linaro.org>

On Wed, Jan 04, 2017 at 07:48:10PM +0800, Baoyou Xie wrote:
> This patch documents devicetree tree bindings for the ZTE zx2967

'devicetree' is good enough, and the 'tree' after it is redundant.

> power domain driver.

DT bindings is to describe hardware block not software, so the word like
'driver' is not appropriate.  So please drop it from here and the patch
subject.

> 
> Signed-off-by: Baoyou Xie <baoyou.xie@linaro.org>
> ---
>  Documentation/devicetree/bindings/soc/zte/pd-2967xx.txt | 17 +++++++++++++++++
>  1 file changed, 17 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/soc/zte/pd-2967xx.txt
> 
> diff --git a/Documentation/devicetree/bindings/soc/zte/pd-2967xx.txt b/Documentation/devicetree/bindings/soc/zte/pd-2967xx.txt
> new file mode 100644
> index 0000000..1476318
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/zte/pd-2967xx.txt
> @@ -0,0 +1,17 @@
> +* ZTE 2967 SoC Power Domains

ZTE ZX2967 family.  And this is a bindings document for PCU, IMO.  And
do we know the full name of abbreviation 'PCU'?  Power Control Unit?

> +
> +2967 processors include support for multiple power domains which are used

ZX2967 family

> +to gate power to one or more peripherals on the processor.
> +
> +Required Properties:
> +- compatible: should be one of the following.
> +    * zte,zx296718-pcu - for zx296718 board's power domain.

Drop 'board's'.

> +- reg: physical base address of the controller and length of memory mapped
> +    region.
> +
> +Example:
> +
> +	pcu_domain: pcu at 0x00117000 {

The unit-address in node name shouldn't have 0x and leading zeros.

	pcu_domain: pcu at 117000 {

Shawn

> +		compatible = "zte,zx296718-pcu";
> +		reg = <0x00117000 0x1000>;
> +	};
> -- 
> 2.7.4
> 

^ permalink raw reply

* [PATCH v6 2/5] MAINTAINERS: add zx2967 SoC drivers to ARM ZTE architecture
From: Shawn Guo @ 2017-01-05  6:49 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483530494-14177-2-git-send-email-baoyou.xie@linaro.org>

On Wed, Jan 04, 2017 at 07:48:11PM +0800, Baoyou Xie wrote:
> Add the ZTE SoC drivers as maintained by ARM ZTE
> architecture maintainers, as they're parts of the core IP.
> 
> By the way, this patch adds the maintainer for ARM
> ZTE architecture to Baoyou Xie.
> 
> Signed-off-by: Baoyou Xie <baoyou.xie@linaro.org>
> ---
>  MAINTAINERS | 5 +++++
>  1 file changed, 5 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ad199da..2593296 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1975,12 +1975,17 @@ F:	arch/arm/mach-pxa/include/mach/z2.h
>  
>  ARM/ZTE ARCHITECTURE
>  M:	Jun Nie <jun.nie@linaro.org>
> +M:	Baoyou Xie <baoyou.xie@linaro.org>
>  L:	linux-arm-kernel at lists.infradead.org (moderated for non-subscribers)
>  S:	Maintained
>  F:	arch/arm/mach-zx/
>  F:	drivers/clk/zte/
> +F:	drivers/soc/zte/
> +F:	drivers/thermal/zx*

This line is about thermal support, and shouldn't be added in this
patch series.

Shawn

>  F:	Documentation/devicetree/bindings/arm/zte.txt
>  F:	Documentation/devicetree/bindings/clock/zx296702-clk.txt
> +F:	Documentation/devicetree/bindings/soc/zte/
> +F:	include/dt-bindings/soc/zx*.h
>  
>  ARM/ZYNQ ARCHITECTURE
>  M:	Michal Simek <michal.simek@xilinx.com>
> -- 
> 2.7.4
> 

^ permalink raw reply

* [PATCH v6 4/5] soc: zte: pm_domains: Prepare for supporting ARMv8 zx2967 family
From: Shawn Guo @ 2017-01-05  7:05 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483530494-14177-4-git-send-email-baoyou.xie@linaro.org>

On Wed, Jan 04, 2017 at 07:48:13PM +0800, Baoyou Xie wrote:
> The ARMv8 zx2967 family (296718, 296716 etc) uses different value
> for controlling the power domain on/off registers, Choose the
> value depending on the compatible.
> 
> Multiple domains are prepared for the family, this patch prepares
> the common functions.
> 
> Signed-off-by: Baoyou Xie <baoyou.xie@linaro.org>
> ---
>  drivers/soc/Kconfig                 |   1 +
>  drivers/soc/Makefile                |   1 +
>  drivers/soc/zte/Kconfig             |  13 ++++
>  drivers/soc/zte/Makefile            |   4 +
>  drivers/soc/zte/zx2967_pm_domains.c | 142 ++++++++++++++++++++++++++++++++++++
>  drivers/soc/zte/zx2967_pm_domains.h |  44 +++++++++++
>  6 files changed, 205 insertions(+)
>  create mode 100644 drivers/soc/zte/Kconfig
>  create mode 100644 drivers/soc/zte/Makefile
>  create mode 100644 drivers/soc/zte/zx2967_pm_domains.c
>  create mode 100644 drivers/soc/zte/zx2967_pm_domains.h
> 
> diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
> index f31bceb..f09023f 100644
> --- a/drivers/soc/Kconfig
> +++ b/drivers/soc/Kconfig
> @@ -11,5 +11,6 @@ source "drivers/soc/tegra/Kconfig"
>  source "drivers/soc/ti/Kconfig"
>  source "drivers/soc/ux500/Kconfig"
>  source "drivers/soc/versatile/Kconfig"
> +source "drivers/soc/zte/Kconfig"
>  
>  endmenu
> diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
> index 50c23d0..05eae52 100644
> --- a/drivers/soc/Makefile
> +++ b/drivers/soc/Makefile
> @@ -16,3 +16,4 @@ obj-$(CONFIG_ARCH_TEGRA)	+= tegra/
>  obj-$(CONFIG_SOC_TI)		+= ti/
>  obj-$(CONFIG_ARCH_U8500)	+= ux500/
>  obj-$(CONFIG_PLAT_VERSATILE)	+= versatile/
> +obj-$(CONFIG_ARCH_ZX)		+= zte/
> diff --git a/drivers/soc/zte/Kconfig b/drivers/soc/zte/Kconfig
> new file mode 100644
> index 0000000..20bde38
> --- /dev/null
> +++ b/drivers/soc/zte/Kconfig
> @@ -0,0 +1,13 @@
> +#
> +# ZTE SoC drivers
> +#
> +menuconfig SOC_ZTE
> +	bool "ZTE SoC driver support"
> +
> +if SOC_ZTE
> +
> +config ZX2967_PM_DOMAINS
> +	bool "ZX2967 PM domains"
> +	depends on PM_GENERIC_DOMAINS
> +
> +endif
> diff --git a/drivers/soc/zte/Makefile b/drivers/soc/zte/Makefile
> new file mode 100644
> index 0000000..8a37f2f
> --- /dev/null
> +++ b/drivers/soc/zte/Makefile
> @@ -0,0 +1,4 @@
> +#
> +# ZTE SOC drivers
> +#
> +obj-$(CONFIG_ZX2967_PM_DOMAINS) += zx2967_pm_domains.o
> diff --git a/drivers/soc/zte/zx2967_pm_domains.c b/drivers/soc/zte/zx2967_pm_domains.c
> new file mode 100644
> index 0000000..f190a62
> --- /dev/null
> +++ b/drivers/soc/zte/zx2967_pm_domains.c
> @@ -0,0 +1,142 @@
> +/*
> + * Copyright (C) 2017 ZTE Ltd.
> + *
> + * Author: Baoyou Xie <baoyou.xie@linaro.org>
> + * License terms: GNU General Public License (GPL) version 2
> + */
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +
> +#include "zx2967_pm_domains.h"
> +
> +#define PCU_DM_CLKEN(zpd)	((zpd)->reg_offset[REG_CLKEN])
> +#define PCU_DM_ISOEN(zpd)	((zpd)->reg_offset[REG_ISOEN])
> +#define PCU_DM_RSTEN(zpd)	((zpd)->reg_offset[REG_RSTEN])
> +#define PCU_DM_PWREN(zpd)	((zpd)->reg_offset[REG_PWREN])
> +#define PCU_DM_ACK_SYNC(zpd)	((zpd)->reg_offset[REG_ACK_SYNC])
> +
> +static void __iomem *pcubase;
> +
> +int zx2967_power_on(struct generic_pm_domain *domain)

The function is now used only in this file, so it can be static.

> +{
> +	struct zx2967_pm_domain *zpd = (struct zx2967_pm_domain *)domain;
> +	unsigned long loop = 1000;
> +	u32 val;
> +
> +	val = readl_relaxed(pcubase + PCU_DM_PWREN(zpd));
> +	if (zpd->polarity == PWREN)
> +		val |= BIT(zpd->bit);
> +	else
> +		val &= ~BIT(zpd->bit);
> +	writel_relaxed(val, pcubase + PCU_DM_PWREN(zpd));
> +
> +	do {
> +		udelay(1);
> +		val = readl_relaxed(pcubase + PCU_DM_ACK_SYNC(zpd))
> +				   & BIT(zpd->bit);
> +	} while (--loop && !val);
> +
> +	if (!loop) {
> +		pr_err("Error: %s %s fail\n", __func__, domain->name);
> +		return -EIO;
> +	}
> +
> +	val = readl_relaxed(pcubase + PCU_DM_RSTEN(zpd));
> +	val |= BIT(zpd->bit);
> +	writel_relaxed(val, pcubase + PCU_DM_RSTEN(zpd));
> +	udelay(5);
> +
> +	val = readl_relaxed(pcubase + PCU_DM_ISOEN(zpd));
> +	val &= ~BIT(zpd->bit);
> +	writel_relaxed(val, pcubase + PCU_DM_ISOEN(zpd));
> +	udelay(5);
> +
> +	val = readl_relaxed(pcubase + PCU_DM_CLKEN(zpd));
> +	val |= BIT(zpd->bit);
> +	writel_relaxed(val, pcubase + PCU_DM_CLKEN(zpd));
> +	udelay(5);
> +
> +	pr_debug("poweron %s\n", domain->name);
> +
> +	return 0;
> +}
> +
> +int zx2967_power_off(struct generic_pm_domain *domain)

Ditto

Shawn

> +{
> +	struct zx2967_pm_domain *zpd = (struct zx2967_pm_domain *)domain;
> +	unsigned long loop = 1000;
> +	u32 val;
> +
> +	val = readl_relaxed(pcubase + PCU_DM_CLKEN(zpd));
> +	val &= ~BIT(zpd->bit);
> +	writel_relaxed(val, pcubase + PCU_DM_CLKEN(zpd));
> +	udelay(5);
> +
> +	val = readl_relaxed(pcubase + PCU_DM_ISOEN(zpd));
> +	val |= BIT(zpd->bit);
> +	writel_relaxed(val, pcubase + PCU_DM_ISOEN(zpd));
> +	udelay(5);
> +
> +	val = readl_relaxed(pcubase + PCU_DM_RSTEN(zpd));
> +	val &= ~BIT(zpd->bit);
> +	writel_relaxed(val, pcubase + PCU_DM_RSTEN(zpd));
> +	udelay(5);
> +
> +	val = readl_relaxed(pcubase + PCU_DM_PWREN(zpd));
> +	if (zpd->polarity == PWREN)
> +		val &= ~BIT(zpd->bit);
> +	else
> +		val |= BIT(zpd->bit);
> +	writel_relaxed(val, pcubase + PCU_DM_PWREN(zpd));
> +
> +	do {
> +		udelay(1);
> +		val = readl_relaxed(pcubase + PCU_DM_ACK_SYNC(zpd))
> +				   & BIT(zpd->bit);
> +	} while (--loop && val);
> +
> +	if (!loop) {
> +		pr_err("Error: %s %s fail\n", __func__, domain->name);
> +		return -EIO;
> +	}
> +
> +	pr_debug("poweroff %s\n", domain->name);
> +
> +	return 0;
> +}
> +
> +int zx2967_pd_probe(struct platform_device *pdev,
> +		    struct generic_pm_domain **zx_pm_domains,
> +		    int domain_num)
> +{
> +	struct genpd_onecell_data *genpd_data;
> +	struct resource *res;
> +	int i;
> +
> +	genpd_data = devm_kzalloc(&pdev->dev, sizeof(*genpd_data), GFP_KERNEL);
> +	if (!genpd_data)
> +		return -ENOMEM;
> +
> +	genpd_data->domains = zx_pm_domains;
> +	genpd_data->num_domains = domain_num;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	pcubase = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(pcubase)) {
> +		dev_err(&pdev->dev, "ioremap fail.\n");
> +		return PTR_ERR(pcubase);
> +	}
> +
> +	for (i = 0; i < domain_num; ++i) {
> +		zx_pm_domains[i]->power_on = zx2967_power_on;
> +		zx_pm_domains[i]->power_off = zx2967_power_off;
> +
> +		pm_genpd_init(zx_pm_domains[i], NULL, false);
> +	}
> +
> +	of_genpd_add_provider_onecell(pdev->dev.of_node, genpd_data);
> +	dev_info(&pdev->dev, "powerdomain init ok\n");
> +	return 0;
> +}
> diff --git a/drivers/soc/zte/zx2967_pm_domains.h b/drivers/soc/zte/zx2967_pm_domains.h
> new file mode 100644
> index 0000000..cb46595
> --- /dev/null
> +++ b/drivers/soc/zte/zx2967_pm_domains.h
> @@ -0,0 +1,44 @@
> +/*
> + * Header for ZTE's Power Domain Driver support
> + *
> + * Copyright (C) 2017 ZTE Ltd.
> + *
> + * Author: Baoyou Xie <baoyou.xie@linaro.org>
> + * License terms: GNU General Public License (GPL) version 2
> + */
> +
> +#ifndef __ZTE_ZX2967_PM_DOMAIN_H
> +#define __ZTE_ZX2967_PM_DOMAIN_H
> +
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +
> +enum {
> +	REG_CLKEN,
> +	REG_ISOEN,
> +	REG_RSTEN,
> +	REG_PWREN,
> +	REG_PWRDN,
> +	REG_ACK_SYNC,
> +
> +	/* The size of the array - must be last */
> +	REG_ARRAY_SIZE,
> +};
> +
> +enum zx2967_power_polarity {
> +	PWREN,
> +	PWRDN,
> +};
> +
> +struct zx2967_pm_domain {
> +	struct generic_pm_domain dm;
> +	const u16 bit;
> +	const enum zx2967_power_polarity polarity;
> +	const u16 *reg_offset;
> +};
> +
> +int zx2967_pd_probe(struct platform_device *pdev,
> +		    struct generic_pm_domain **zx_pm_domains,
> +		    int domain_num);
> +
> +#endif /* __ZTE_ZX2967_PM_DOMAIN_H */
> -- 
> 2.7.4
> 

^ permalink raw reply

* [PATCH v3 3/3] drm: zte: add overlay plane support
From: Sean Paul @ 2017-01-05  7:26 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1482979048-32037-4-git-send-email-shawnguo@kernel.org>

On Wed, Dec 28, 2016 at 9:37 PM, Shawn Guo <shawnguo@kernel.org> wrote:
> From: Shawn Guo <shawn.guo@linaro.org>
>
> It enables VOU VL (Video Layer) to support overlay plane with scaling
> function.  VL0 has some quirks on scaling support.  We choose to skip it
> and only adds VL1 and VL2 into DRM core for now.
>
> Function zx_plane_atomic_disable() gets moved around with no changes to
> save a forward declaration.
>
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> ---
>  drivers/gpu/drm/zte/zx_plane.c      | 311 +++++++++++++++++++++++++++++++++---
>  drivers/gpu/drm/zte/zx_plane_regs.h |  51 ++++++
>  drivers/gpu/drm/zte/zx_vou.c        |  80 +++++++++-
>  drivers/gpu/drm/zte/zx_vou_regs.h   |  18 +++
>  4 files changed, 431 insertions(+), 29 deletions(-)
>
> diff --git a/drivers/gpu/drm/zte/zx_plane.c b/drivers/gpu/drm/zte/zx_plane.c
> index 5445eebf830f..c5ac42647735 100644
> --- a/drivers/gpu/drm/zte/zx_plane.c
> +++ b/drivers/gpu/drm/zte/zx_plane.c
> @@ -30,6 +30,275 @@
>         DRM_FORMAT_ARGB4444,
>  };
>
> +static const uint32_t vl_formats[] = {
> +       DRM_FORMAT_NV12,        /* Semi-planar YUV420 */
> +       DRM_FORMAT_YUV420,      /* Planar YUV420 */
> +       DRM_FORMAT_YUYV,        /* Packed YUV422 */
> +       DRM_FORMAT_YVYU,
> +       DRM_FORMAT_UYVY,
> +       DRM_FORMAT_VYUY,
> +       DRM_FORMAT_YUV444,      /* YUV444 8bit */
> +       /*
> +        * TODO: add formats below that HW supports:
> +        *  - YUV420 P010
> +        *  - YUV420 Hantro
> +        *  - YUV444 10bit
> +        */
> +};
> +
> +#define FRAC_16_16(mult, div)    (((mult) << 16) / (div))
> +
> +static int zx_vl_plane_atomic_check(struct drm_plane *plane,
> +                                   struct drm_plane_state *plane_state)
> +{
> +       struct drm_framebuffer *fb = plane_state->fb;
> +       struct drm_crtc *crtc = plane_state->crtc;
> +       struct drm_crtc_state *crtc_state;
> +       struct drm_rect clip;
> +       int min_scale = FRAC_16_16(1, 8);
> +       int max_scale = FRAC_16_16(8, 1);
> +
> +       if (!crtc || !fb)
> +               return 0;
> +
> +       crtc_state = drm_atomic_get_existing_crtc_state(plane_state->state,
> +                                                       crtc);
> +       if (WARN_ON(!crtc_state))
> +               return -EINVAL;
> +
> +       /* nothing to check when disabling or disabled */
> +       if (!crtc_state->enable)
> +               return 0;
> +
> +       /* plane must be enabled */
> +       if (!plane_state->crtc)
> +               return -EINVAL;
> +
> +       clip.x1 = 0;
> +       clip.y1 = 0;
> +       clip.x2 = crtc_state->adjusted_mode.hdisplay;
> +       clip.y2 = crtc_state->adjusted_mode.vdisplay;
> +
> +       return drm_plane_helper_check_state(plane_state, &clip,
> +                                           min_scale, max_scale,
> +                                           true, true);
> +}
> +
> +static u32 zx_vl_get_fmt(uint32_t format)
> +{
> +       u32 val = 0;
> +
> +       switch (format) {
> +       case DRM_FORMAT_NV12:
> +               val = VL_FMT_YUV420;
> +               break;
> +       case DRM_FORMAT_YUV420:
> +               val = VL_YUV420_PLANAR | VL_FMT_YUV420;
> +               break;
> +       case DRM_FORMAT_YUYV:
> +               val = VL_YUV422_YUYV | VL_FMT_YUV422;
> +               break;
> +       case DRM_FORMAT_YVYU:
> +               val = VL_YUV422_YVYU | VL_FMT_YUV422;
> +               break;
> +       case DRM_FORMAT_UYVY:
> +               val = VL_YUV422_UYVY | VL_FMT_YUV422;
> +               break;
> +       case DRM_FORMAT_VYUY:
> +               val = VL_YUV422_VYUY | VL_FMT_YUV422;
> +               break;
> +       case DRM_FORMAT_YUV444:
> +               val = VL_FMT_YUV444_8BIT;

Minor nit: You could have eliminated val and just returned directly
from all of the cases. Seems like there are a few other functions this
is also true for.

> +               break;
> +       default:
> +               WARN_ONCE(1, "invalid pixel format %d\n", format);
> +       }
> +
> +       return val;
> +}
> +
> +static inline void zx_vl_set_update(struct zx_plane *zplane)
> +{
> +       void __iomem *layer = zplane->layer;
> +
> +       zx_writel_mask(layer + VL_CTRL0, VL_UPDATE, VL_UPDATE);
> +}
> +
> +static inline void zx_vl_rsz_set_update(struct zx_plane *zplane)
> +{
> +       zx_writel(zplane->rsz + RSZ_VL_ENABLE_CFG, 1);
> +}
> +
> +static u32 zx_vl_rsz_get_fmt(uint32_t format)
> +{
> +       u32 val = 0;
> +
> +       switch (format) {
> +       case DRM_FORMAT_NV12:
> +       case DRM_FORMAT_YUV420:
> +               val = RSZ_VL_FMT_YCBCR420;
> +               break;
> +       case DRM_FORMAT_YUYV:
> +       case DRM_FORMAT_YVYU:
> +       case DRM_FORMAT_UYVY:
> +       case DRM_FORMAT_VYUY:
> +               val = RSZ_VL_FMT_YCBCR422;
> +               break;
> +       case DRM_FORMAT_YUV444:
> +               val = RSZ_VL_FMT_YCBCR444;
> +               break;
> +       default:
> +               WARN_ONCE(1, "invalid pixel format %d\n", format);
> +       }
> +
> +       return val;
> +}
> +
> +static inline u32 rsz_step_value(u32 src, u32 dst)
> +{
> +       u32 val = 0;
> +
> +       if (src == dst)
> +               val = 0;
> +       else if (src < dst)
> +               val = RSZ_PARA_STEP((src << 16) / dst);
> +       else if (src > dst)
> +               val = RSZ_DATA_STEP(src / dst) |
> +                     RSZ_PARA_STEP(((src << 16) / dst) & 0xffff);
> +
> +       return val;
> +}
> +
> +static void zx_vl_rsz_setup(struct zx_plane *zplane, uint32_t format,
> +                           u32 src_w, u32 src_h, u32 dst_w, u32 dst_h)
> +{
> +       void __iomem *rsz = zplane->rsz;
> +       u32 src_chroma_w = src_w;
> +       u32 src_chroma_h = src_h;
> +       u32 fmt;
> +
> +       /* Set up source and destination resolution */
> +       zx_writel(rsz + RSZ_SRC_CFG, RSZ_VER(src_h - 1) | RSZ_HOR(src_w - 1));
> +       zx_writel(rsz + RSZ_DEST_CFG, RSZ_VER(dst_h - 1) | RSZ_HOR(dst_w - 1));
> +
> +       /* Configure data format for VL RSZ */
> +       fmt = zx_vl_rsz_get_fmt(format);
> +       zx_writel_mask(rsz + RSZ_VL_CTRL_CFG, RSZ_VL_FMT_MASK, fmt);
> +
> +       /* Calculate Chroma heigth and width */

s/heigth/height/

> +       if (fmt == RSZ_VL_FMT_YCBCR420) {
> +               src_chroma_w = src_w >> 1;
> +               src_chroma_h = src_h >> 1;
> +       } else if (fmt == RSZ_VL_FMT_YCBCR422) {
> +               src_chroma_w = src_w >> 1;
> +       }
> +
> +       /* Set up Luma and Chroma step registers */
> +       zx_writel(rsz + RSZ_VL_LUMA_HOR, rsz_step_value(src_w, dst_w));
> +       zx_writel(rsz + RSZ_VL_LUMA_VER, rsz_step_value(src_h, dst_h));
> +       zx_writel(rsz + RSZ_VL_CHROMA_HOR, rsz_step_value(src_chroma_w, dst_w));
> +       zx_writel(rsz + RSZ_VL_CHROMA_VER, rsz_step_value(src_chroma_h, dst_h));
> +
> +       zx_vl_rsz_set_update(zplane);
> +}
> +
> +static void zx_vl_plane_atomic_update(struct drm_plane *plane,
> +                                     struct drm_plane_state *old_state)
> +{
> +       struct zx_plane *zplane = to_zx_plane(plane);
> +       struct drm_plane_state *state = plane->state;
> +       struct drm_framebuffer *fb = state->fb;
> +       struct drm_rect *src = &state->src;
> +       struct drm_rect *dst = &state->dst;
> +       struct drm_gem_cma_object *cma_obj;
> +       void __iomem *layer = zplane->layer;
> +       void __iomem *hbsc = zplane->hbsc;
> +       void __iomem *paddr_reg;
> +       dma_addr_t paddr;
> +       u32 src_x, src_y, src_w, src_h;
> +       u32 dst_x, dst_y, dst_w, dst_h;
> +       uint32_t format;
> +       u32 fmt;
> +       int num_planes;
> +       int i;
> +
> +       if (!fb)
> +               return;
> +
> +       format = fb->pixel_format;
> +
> +       src_x = src->x1 >> 16;
> +       src_y = src->y1 >> 16;
> +       src_w = drm_rect_width(src) >> 16;
> +       src_h = drm_rect_height(src) >> 16;
> +
> +       dst_x = dst->x1;
> +       dst_y = dst->y1;
> +       dst_w = drm_rect_width(dst);
> +       dst_h = drm_rect_height(dst);
> +
> +       /* Set up data address registers for Y, Cb and Cr planes */
> +       num_planes = drm_format_num_planes(format);
> +       paddr_reg = layer + VL_Y;
> +       for (i = 0; i < num_planes; i++) {
> +               cma_obj = drm_fb_cma_get_gem_obj(fb, i);
> +               paddr = cma_obj->paddr + fb->offsets[i];
> +               paddr += src_y * fb->pitches[i];
> +               paddr += src_x * drm_format_plane_cpp(format, i);
> +               zx_writel(paddr_reg, paddr);
> +               paddr_reg += 4;
> +       }
> +
> +       /* Set up source height/width register */
> +       zx_writel(layer + VL_SRC_SIZE, GL_SRC_W(src_w) | GL_SRC_H(src_h));
> +
> +       /* Set up start position register */
> +       zx_writel(layer + VL_POS_START, GL_POS_X(dst_x) | GL_POS_Y(dst_y));
> +
> +       /* Set up end position register */
> +       zx_writel(layer + VL_POS_END,
> +                 GL_POS_X(dst_x + dst_w) | GL_POS_Y(dst_y + dst_h));
> +
> +       /* Strides of Cb and Cr planes should be identical */
> +       zx_writel(layer + VL_STRIDE, LUMA_STRIDE(fb->pitches[0]) |
> +                 CHROMA_STRIDE(fb->pitches[1]));
> +
> +       /* Set up video layer data format */
> +       fmt = zx_vl_get_fmt(format);
> +       zx_writel(layer + VL_CTRL1, fmt);
> +
> +       /* Always use scaler since it exists (set for not bypass) */
> +       zx_writel_mask(layer + VL_CTRL2, VL_SCALER_BYPASS_MODE,
> +                      VL_SCALER_BYPASS_MODE);
> +
> +       zx_vl_rsz_setup(zplane, format, src_w, src_h, dst_w, dst_h);
> +
> +       /* Enable HBSC block */
> +       zx_writel_mask(hbsc + HBSC_CTRL0, HBSC_CTRL_EN, HBSC_CTRL_EN);
> +
> +       zx_vou_layer_enable(plane);
> +
> +       zx_vl_set_update(zplane);
> +}
> +
> +static void zx_plane_atomic_disable(struct drm_plane *plane,
> +                                   struct drm_plane_state *old_state)
> +{
> +       struct zx_plane *zplane = to_zx_plane(plane);
> +       void __iomem *hbsc = zplane->hbsc;
> +
> +       zx_vou_layer_disable(plane);
> +
> +       /* Disable HBSC block */
> +       zx_writel_mask(hbsc + HBSC_CTRL0, HBSC_CTRL_EN, 0);
> +}
> +
> +static const struct drm_plane_helper_funcs zx_vl_plane_helper_funcs = {
> +       .atomic_check = zx_vl_plane_atomic_check,
> +       .atomic_update = zx_vl_plane_atomic_update,
> +       .atomic_disable = zx_plane_atomic_disable,
> +};
> +
>  static int zx_gl_plane_atomic_check(struct drm_plane *plane,
>                                     struct drm_plane_state *plane_state)
>  {
> @@ -97,14 +366,6 @@ static inline void zx_gl_rsz_set_update(struct zx_plane *zplane)
>         zx_writel(zplane->rsz + RSZ_ENABLE_CFG, 1);
>  }
>
> -void zx_plane_set_update(struct drm_plane *plane)
> -{
> -       struct zx_plane *zplane = to_zx_plane(plane);
> -
> -       zx_gl_rsz_set_update(zplane);
> -       zx_gl_set_update(zplane);
> -}
> -
>  static void zx_gl_rsz_setup(struct zx_plane *zplane, u32 src_w, u32 src_h,
>                             u32 dst_w, u32 dst_h)
>  {
> @@ -202,18 +463,6 @@ static void zx_gl_plane_atomic_update(struct drm_plane *plane,
>         zx_gl_set_update(zplane);
>  }
>
> -static void zx_plane_atomic_disable(struct drm_plane *plane,
> -                                   struct drm_plane_state *old_state)
> -{
> -       struct zx_plane *zplane = to_zx_plane(plane);
> -       void __iomem *hbsc = zplane->hbsc;
> -
> -       zx_vou_layer_disable(plane);
> -
> -       /* Disable HBSC block */
> -       zx_writel_mask(hbsc + HBSC_CTRL0, HBSC_CTRL_EN, 0);
> -}
> -
>  static const struct drm_plane_helper_funcs zx_gl_plane_helper_funcs = {
>         .atomic_check = zx_gl_plane_atomic_check,
>         .atomic_update = zx_gl_plane_atomic_update,
> @@ -235,6 +484,24 @@ static void zx_plane_destroy(struct drm_plane *plane)
>         .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
>  };
>
> +void zx_plane_set_update(struct drm_plane *plane)
> +{
> +       struct zx_plane *zplane = to_zx_plane(plane);
> +
> +       switch (plane->type) {
> +       case DRM_PLANE_TYPE_PRIMARY:
> +               zx_gl_rsz_set_update(zplane);
> +               zx_gl_set_update(zplane);
> +               break;
> +       case DRM_PLANE_TYPE_OVERLAY:
> +               zx_vl_rsz_set_update(zplane);
> +               zx_vl_set_update(zplane);
> +               break;
> +       default:
> +               WARN_ONCE(1, "unsupported plane type %d\n", plane->type);
> +       }
> +}
> +
>  static void zx_plane_hbsc_init(struct zx_plane *zplane)
>  {
>         void __iomem *hbsc = zplane->hbsc;
> @@ -272,7 +539,9 @@ int zx_plane_init(struct drm_device *drm, struct zx_plane *zplane,
>                 format_count = ARRAY_SIZE(gl_formats);
>                 break;
>         case DRM_PLANE_TYPE_OVERLAY:
> -               /* TODO: add video layer (vl) support */
> +               helper = &zx_vl_plane_helper_funcs;
> +               formats = vl_formats;
> +               format_count = ARRAY_SIZE(vl_formats);
>                 break;
>         default:
>                 return -ENODEV;
> diff --git a/drivers/gpu/drm/zte/zx_plane_regs.h b/drivers/gpu/drm/zte/zx_plane_regs.h
> index 3dde6716a558..65f271aeabed 100644
> --- a/drivers/gpu/drm/zte/zx_plane_regs.h
> +++ b/drivers/gpu/drm/zte/zx_plane_regs.h
> @@ -46,6 +46,37 @@
>  #define GL_POS_X(x)    (((x) << GL_POS_X_SHIFT) & GL_POS_X_MASK)
>  #define GL_POS_Y(x)    (((x) << GL_POS_Y_SHIFT) & GL_POS_Y_MASK)
>
> +/* VL registers */
> +#define VL_CTRL0                       0x00
> +#define VL_UPDATE                      BIT(3)
> +#define VL_CTRL1                       0x04
> +#define VL_YUV420_PLANAR               BIT(5)
> +#define VL_YUV422_SHIFT                        3
> +#define VL_YUV422_YUYV                 (0 << VL_YUV422_SHIFT)
> +#define VL_YUV422_YVYU                 (1 << VL_YUV422_SHIFT)
> +#define VL_YUV422_UYVY                 (2 << VL_YUV422_SHIFT)
> +#define VL_YUV422_VYUY                 (3 << VL_YUV422_SHIFT)
> +#define VL_FMT_YUV420                  0
> +#define VL_FMT_YUV422                  1
> +#define VL_FMT_YUV420_P010             2
> +#define VL_FMT_YUV420_HANTRO           3
> +#define VL_FMT_YUV444_8BIT             4
> +#define VL_FMT_YUV444_10BIT            5
> +#define VL_CTRL2                       0x08
> +#define VL_SCALER_BYPASS_MODE          BIT(0)
> +#define VL_STRIDE                      0x0c
> +#define LUMA_STRIDE_SHIFT              16
> +#define LUMA_STRIDE_MASK               (0xffff << LUMA_STRIDE_SHIFT)
> +#define CHROMA_STRIDE_SHIFT            0
> +#define CHROMA_STRIDE_MASK             (0xffff << CHROMA_STRIDE_SHIFT)
> +#define VL_SRC_SIZE                    0x10
> +#define VL_Y                           0x14
> +#define VL_POS_START                   0x30
> +#define VL_POS_END                     0x34
> +
> +#define LUMA_STRIDE(x)  (((x) << LUMA_STRIDE_SHIFT) & LUMA_STRIDE_MASK)
> +#define CHROMA_STRIDE(x) (((x) << CHROMA_STRIDE_SHIFT) & CHROMA_STRIDE_MASK)
> +
>  /* CSC registers */
>  #define CSC_CTRL0                      0x30
>  #define CSC_COV_MODE_SHIFT             16
> @@ -69,6 +100,18 @@
>  #define RSZ_DEST_CFG                   0x04
>  #define RSZ_ENABLE_CFG                 0x14
>
> +#define RSZ_VL_LUMA_HOR                        0x08
> +#define RSZ_VL_LUMA_VER                        0x0c
> +#define RSZ_VL_CHROMA_HOR              0x10
> +#define RSZ_VL_CHROMA_VER              0x14
> +#define RSZ_VL_CTRL_CFG                        0x18
> +#define RSZ_VL_FMT_SHIFT               3
> +#define RSZ_VL_FMT_MASK                        (0x3 << RSZ_VL_FMT_SHIFT)
> +#define RSZ_VL_FMT_YCBCR420            (0x0 << RSZ_VL_FMT_SHIFT)
> +#define RSZ_VL_FMT_YCBCR422            (0x1 << RSZ_VL_FMT_SHIFT)
> +#define RSZ_VL_FMT_YCBCR444            (0x2 << RSZ_VL_FMT_SHIFT)
> +#define RSZ_VL_ENABLE_CFG              0x1c
> +
>  #define RSZ_VER_SHIFT                  16
>  #define RSZ_VER_MASK                   (0xffff << RSZ_VER_SHIFT)
>  #define RSZ_HOR_SHIFT                  0
> @@ -77,6 +120,14 @@
>  #define RSZ_VER(x)     (((x) << RSZ_VER_SHIFT) & RSZ_VER_MASK)
>  #define RSZ_HOR(x)     (((x) << RSZ_HOR_SHIFT) & RSZ_HOR_MASK)
>
> +#define RSZ_DATA_STEP_SHIFT            16
> +#define RSZ_DATA_STEP_MASK             (0xffff << RSZ_DATA_STEP_SHIFT)
> +#define RSZ_PARA_STEP_SHIFT            0
> +#define RSZ_PARA_STEP_MASK             (0xffff << RSZ_PARA_STEP_SHIFT)
> +
> +#define RSZ_DATA_STEP(x) (((x) << RSZ_DATA_STEP_SHIFT) & RSZ_DATA_STEP_MASK)
> +#define RSZ_PARA_STEP(x) (((x) << RSZ_PARA_STEP_SHIFT) & RSZ_PARA_STEP_MASK)
> +
>  /* HBSC registers */
>  #define HBSC_SATURATION                        0x00
>  #define HBSC_HUE                       0x04
> diff --git a/drivers/gpu/drm/zte/zx_vou.c b/drivers/gpu/drm/zte/zx_vou.c
> index 3fb4fc04e693..e832c2ec3156 100644
> --- a/drivers/gpu/drm/zte/zx_vou.c
> +++ b/drivers/gpu/drm/zte/zx_vou.c
> @@ -84,6 +84,8 @@ struct zx_crtc_bits {
>  struct zx_crtc {
>         struct drm_crtc crtc;
>         struct drm_plane *primary;
> +       struct drm_plane *overlay_active[VL_NUM];
> +       unsigned int overlay_active_num;

I don't think this belongs here. You can instead add an active (or
enabled) bool to the zx_plane struct and keep track of it via
atomic_plane_update/disable. This allows you to call
zx_plane_set_update unconditionally in the vou irq handler and check
active/enabled in zx_plane_set_update.

This also seems potentially racey, so be careful.


>         struct zx_vou_hw *vou;
>         void __iomem *chnreg;
>         const struct zx_crtc_regs *regs;
> @@ -112,6 +114,22 @@ struct vou_layer_bits {
>         },
>  };
>
> +static const struct vou_layer_bits zx_vl_bits[VL_NUM] = {
> +       {
> +               .enable = OSD_CTRL0_VL0_EN,
> +               .chnsel = OSD_CTRL0_VL0_SEL,
> +               .clksel = VOU_CLK_VL0_SEL,
> +       }, {
> +               .enable = OSD_CTRL0_VL1_EN,
> +               .chnsel = OSD_CTRL0_VL1_SEL,
> +               .clksel = VOU_CLK_VL1_SEL,
> +       }, {
> +               .enable = OSD_CTRL0_VL2_EN,
> +               .chnsel = OSD_CTRL0_VL2_SEL,
> +               .clksel = VOU_CLK_VL2_SEL,
> +       },
> +};
> +
>  struct zx_vou_hw {
>         struct device *dev;
>         void __iomem *osd;
> @@ -439,6 +457,9 @@ void zx_vou_layer_enable(struct drm_plane *plane)
>         }
>
>         zx_writel_mask(vou->osd + OSD_CTRL0, bits->enable, bits->enable);
> +
> +       if (plane->type == DRM_PLANE_TYPE_OVERLAY)
> +               zcrtc->overlay_active[zcrtc->overlay_active_num++] = plane;
>  }
>
>  void zx_vou_layer_disable(struct drm_plane *plane)
> @@ -449,6 +470,51 @@ void zx_vou_layer_disable(struct drm_plane *plane)
>         const struct vou_layer_bits *bits = zplane->bits;
>
>         zx_writel_mask(vou->osd + OSD_CTRL0, bits->enable, 0);
> +
> +       if (plane->type == DRM_PLANE_TYPE_OVERLAY)
> +               zcrtc->overlay_active[zcrtc->overlay_active_num--] = NULL;
> +}
> +
> +static void zx_overlay_init(struct drm_device *drm, struct zx_vou_hw *vou)
> +{
> +       struct device *dev = vou->dev;
> +       struct zx_plane *zplane;
> +       int i;
> +       int ret;
> +
> +       /*
> +        * VL0 has some quirks on scaling support which need special handling.
> +        * Let's leave it out for now.
> +        */
> +       for (i = 1; i < VL_NUM; i++) {
> +               zplane = devm_kzalloc(dev, sizeof(*zplane), GFP_KERNEL);
> +               if (!zplane) {
> +                       DRM_DEV_ERROR(dev, "failed to allocate zplane %d\n", i);
> +                       return;
> +               }
> +
> +               zplane->layer = vou->osd + OSD_VL_OFFSET(i);
> +               zplane->hbsc = vou->osd + HBSC_VL_OFFSET(i);
> +               zplane->rsz = vou->otfppu + RSZ_VL_OFFSET(i);
> +               zplane->bits = &zx_vl_bits[i];
> +
> +               ret = zx_plane_init(drm, zplane, DRM_PLANE_TYPE_OVERLAY);
> +               if (ret) {
> +                       DRM_DEV_ERROR(dev, "failed to init overlay %d\n", i);
> +                       continue;
> +               }
> +       }
> +}
> +
> +static inline void zx_osd_int_update(struct zx_crtc *zcrtc)
> +{
> +       int i;
> +
> +       vou_chn_set_update(zcrtc);
> +       zx_plane_set_update(zcrtc->primary);
> +
> +       for (i = 0; i < zcrtc->overlay_active_num; i++)
> +               zx_plane_set_update(zcrtc->overlay_active[i]);
>  }
>
>  static irqreturn_t vou_irq_handler(int irq, void *dev_id)
> @@ -470,15 +536,11 @@ static irqreturn_t vou_irq_handler(int irq, void *dev_id)
>         state = zx_readl(vou->osd + OSD_INT_STA);
>         zx_writel(vou->osd + OSD_INT_CLRSTA, state);
>
> -       if (state & OSD_INT_MAIN_UPT) {
> -               vou_chn_set_update(vou->main_crtc);
> -               zx_plane_set_update(vou->main_crtc->primary);
> -       }
> +       if (state & OSD_INT_MAIN_UPT)
> +               zx_osd_int_update(vou->main_crtc);
>
> -       if (state & OSD_INT_AUX_UPT) {
> -               vou_chn_set_update(vou->aux_crtc);
> -               zx_plane_set_update(vou->aux_crtc->primary);
> -       }
> +       if (state & OSD_INT_AUX_UPT)
> +               zx_osd_int_update(vou->aux_crtc);
>
>         if (state & OSD_INT_ERROR)
>                 DRM_DEV_ERROR(vou->dev, "OSD ERROR: 0x%08x!\n", state);
> @@ -648,6 +710,8 @@ static int zx_crtc_bind(struct device *dev, struct device *master, void *data)
>                 goto disable_ppu_clk;
>         }
>
> +       zx_overlay_init(drm, vou);
> +
>         return 0;
>
>  disable_ppu_clk:
> diff --git a/drivers/gpu/drm/zte/zx_vou_regs.h b/drivers/gpu/drm/zte/zx_vou_regs.h
> index f44e7a4ae441..193c1ce01fe7 100644
> --- a/drivers/gpu/drm/zte/zx_vou_regs.h
> +++ b/drivers/gpu/drm/zte/zx_vou_regs.h
> @@ -22,6 +22,15 @@
>  #define AUX_HBSC_OFFSET                        0x860
>  #define AUX_RSZ_OFFSET                 0x800
>
> +#define OSD_VL0_OFFSET                 0x040
> +#define OSD_VL_OFFSET(i)               (OSD_VL0_OFFSET + 0x050 * (i))
> +
> +#define HBSC_VL0_OFFSET                        0x760
> +#define HBSC_VL_OFFSET(i)              (HBSC_VL0_OFFSET + 0x040 * (i))
> +
> +#define RSZ_VL1_U0                     0xa00
> +#define RSZ_VL_OFFSET(i)               (RSZ_VL1_U0 + 0x200 * (i))
> +
>  /* OSD (GPC_GLOBAL) registers */
>  #define OSD_INT_STA                    0x04
>  #define OSD_INT_CLRSTA                 0x08
> @@ -42,6 +51,12 @@
>  )
>  #define OSD_INT_ENABLE (OSD_INT_ERROR | OSD_INT_AUX_UPT | OSD_INT_MAIN_UPT)
>  #define OSD_CTRL0                      0x10
> +#define OSD_CTRL0_VL0_EN               BIT(13)
> +#define OSD_CTRL0_VL0_SEL              BIT(12)
> +#define OSD_CTRL0_VL1_EN               BIT(11)
> +#define OSD_CTRL0_VL1_SEL              BIT(10)
> +#define OSD_CTRL0_VL2_EN               BIT(9)
> +#define OSD_CTRL0_VL2_SEL              BIT(8)
>  #define OSD_CTRL0_GL0_EN               BIT(7)
>  #define OSD_CTRL0_GL0_SEL              BIT(6)
>  #define OSD_CTRL0_GL1_EN               BIT(5)
> @@ -146,6 +161,9 @@
>  #define VOU_INF_DATA_SEL               0x08
>  #define VOU_SOFT_RST                   0x14
>  #define VOU_CLK_SEL                    0x18
> +#define VOU_CLK_VL2_SEL                        BIT(8)
> +#define VOU_CLK_VL1_SEL                        BIT(7)
> +#define VOU_CLK_VL0_SEL                        BIT(6)
>  #define VOU_CLK_GL1_SEL                        BIT(5)
>  #define VOU_CLK_GL0_SEL                        BIT(4)
>  #define VOU_CLK_REQEN                  0x20
> --
> 1.9.1
>



-- 
Sean Paul, Software Engineer, Google / Chromium OS

^ permalink raw reply


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