* [PATCH v2 02/11] mfd: axp20x: add volatile and writeable reg ranges for VBUS power supply driver
From: Quentin Schulz @ 2016-12-09 11:04 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161209110419.28981-1-quentin.schulz@free-electrons.com>
The X-Powers AXP20X and AXP22X PMICs allow to choose the maximum voltage
and minimum current delivered by the VBUS power supply.
This adds the register used by the VBUS power supply driver to the range
of volatile and writeable regs ranges.
Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
---
added in v2
drivers/mfd/axp20x.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
index ba130be..6ee2cc6 100644
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -65,12 +65,14 @@ 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_DCDC_MODE, AXP20X_FG_RES),
regmap_reg_range(AXP20X_RDC_H, AXP20X_OCV(AXP20X_OCV_MAX)),
};
static const struct regmap_range axp20x_volatile_ranges[] = {
regmap_reg_range(AXP20X_PWR_INPUT_STATUS, AXP20X_USB_OTG_STATUS),
+ regmap_reg_range(AXP20X_VBUS_IPSOUT_MGMT, AXP20X_VBUS_IPSOUT_MGMT),
regmap_reg_range(AXP20X_CHRG_CTRL1, AXP20X_CHRG_CTRL2),
regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IRQ5_STATE),
regmap_reg_range(AXP20X_ACIN_V_ADC_H, AXP20X_IPSOUT_V_HIGH_L),
@@ -91,11 +93,13 @@ 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_DCDC_MODE, AXP22X_BATLOW_THRES1),
};
static const struct regmap_range axp22x_volatile_ranges[] = {
regmap_reg_range(AXP20X_PWR_INPUT_STATUS, AXP20X_PWR_OP_MODE),
+ regmap_reg_range(AXP20X_VBUS_IPSOUT_MGMT, AXP20X_VBUS_IPSOUT_MGMT),
regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IRQ5_STATE),
regmap_reg_range(AXP22X_GPIO_STATE, AXP22X_GPIO_STATE),
regmap_reg_range(AXP20X_FG_RES, AXP20X_FG_RES),
--
2.9.3
^ permalink raw reply related
* [PATCH v2 01/11] power: supply: axp20x_usb_power: use of_device_id data field instead of device_is_compatible
From: Quentin Schulz @ 2016-12-09 11:04 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161209110419.28981-1-quentin.schulz@free-electrons.com>
This replaces calls to of_device_is_compatible to check data field of
of_device_id matched when probing the driver.
Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
---
drivers/power/supply/axp20x_usb_power.c | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c
index 6af6feb..8985646 100644
--- a/drivers/power/supply/axp20x_usb_power.c
+++ b/drivers/power/supply/axp20x_usb_power.c
@@ -17,6 +17,7 @@
#include <linux/mfd/axp20x.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
@@ -45,6 +46,7 @@ struct axp20x_usb_power {
struct device_node *np;
struct regmap *regmap;
struct power_supply *supply;
+ int axp20x_id;
};
static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
@@ -86,8 +88,7 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
switch (v & AXP20X_VBUS_CLIMIT_MASK) {
case AXP20X_VBUC_CLIMIT_100mA:
- if (of_device_is_compatible(power->np,
- "x-powers,axp202-usb-power-supply")) {
+ if (power->axp20x_id == AXP202_ID) {
val->intval = 100000;
} else {
val->intval = -1; /* No 100mA limit */
@@ -130,8 +131,7 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
val->intval = POWER_SUPPLY_HEALTH_GOOD;
- if (of_device_is_compatible(power->np,
- "x-powers,axp202-usb-power-supply")) {
+ if (power->axp20x_id == AXP202_ID) {
ret = regmap_read(power->regmap,
AXP20X_USB_OTG_STATUS, &v);
if (ret)
@@ -214,11 +214,12 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
if (!power)
return -ENOMEM;
+ power->axp20x_id = (int)of_device_get_match_data(&pdev->dev);
+
power->np = pdev->dev.of_node;
power->regmap = axp20x->regmap;
- if (of_device_is_compatible(power->np,
- "x-powers,axp202-usb-power-supply")) {
+ if (power->axp20x_id == AXP202_ID) {
/* Enable vbus valid checking */
ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON,
AXP20X_VBUS_MON_VBUS_VALID,
@@ -235,8 +236,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
usb_power_desc = &axp20x_usb_power_desc;
irq_names = axp20x_irq_names;
- } else if (of_device_is_compatible(power->np,
- "x-powers,axp221-usb-power-supply")) {
+ } else if (power->axp20x_id == AXP221_ID) {
usb_power_desc = &axp22x_usb_power_desc;
irq_names = axp22x_irq_names;
} else {
@@ -273,9 +273,13 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
}
static const struct of_device_id axp20x_usb_power_match[] = {
- { .compatible = "x-powers,axp202-usb-power-supply" },
- { .compatible = "x-powers,axp221-usb-power-supply" },
- { }
+ {
+ .compatible = "x-powers,axp202-usb-power-supply",
+ .data = (void *)AXP202_ID,
+ }, {
+ .compatible = "x-powers,axp221-usb-power-supply",
+ .data = (void *)AXP221_ID,
+ }, { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, axp20x_usb_power_match);
--
2.9.3
^ permalink raw reply related
* [PATCH v2 00/11] add support for VBUS max current and min voltage limits AXP20X and AXP22X PMICs
From: Quentin Schulz @ 2016-12-09 11:04 UTC (permalink / raw)
To: linux-arm-kernel
The X-Powers AXP209 and AXP20X PMICs are able to set a limit for the
VBUS power supply for both max current and min voltage supplied. This
series of patch adds the possibility to set these limits from sysfs.
Also, the AXP223 PMIC shares most of its behaviour with the AXP221 but
the former can set the VBUS power supply max current to 100mA, unlike
the latter. The AXP223 VBUS power supply driver used to probe on the
AXP221 compatible. This series of patch introduces a new compatible for
the AXP223 to be able to set the current max limit to 100mA.
With that new compatible, boards having the AXP223 see their DT updated
to use the VBUS power supply driver with the correct compatible.
This series of patch also migrates from of_device_is_compatible function
to the data field of of_device_id to identify the compatible used to
probe. This improves the code readability.
Mostly cosmetic changes in v2 and adding volatile and writeable regs to
AXP20X and AXP22X MFD cells for the VBUS power supply driver.
Quentin Schulz (11):
power: supply: axp20x_usb_power: use of_device_id data field instead
of device_is_compatible
mfd: axp20x: add volatile and writeable reg ranges for VBUS power
supply driver
power: supply: axp20x_usb_power: set min voltage and max current from
sysfs
Documentation: DT: binding: axp20x_usb_power: add axp223 compatible
power: supply: axp20x_usb_power: add 100mA max current limit for
AXP223
mfd: axp20x: add separate MFD cell for AXP223
ARM: dtsi: add DTSI for AXP223
ARM: dts: sun8i-a33-olinuxino: use AXP223 DTSI
ARM: dts: sun8i-a33-sinlinx-sina33: use AXP223 DTSI
ARM: dts: sun8i-r16-parrot: use AXP223 DTSI
ARM: dtsi: sun8i-reference-design-tablet: use AXP223 DTSI
.../bindings/power/supply/axp20x_usb_power.txt | 5 +
arch/arm/boot/dts/axp223.dtsi | 58 +++++++++++
arch/arm/boot/dts/sun8i-a33-olinuxino.dts | 2 +-
arch/arm/boot/dts/sun8i-a33-sinlinx-sina33.dts | 2 +-
arch/arm/boot/dts/sun8i-r16-parrot.dts | 2 +-
.../boot/dts/sun8i-reference-design-tablet.dtsi | 2 +-
drivers/mfd/axp20x.c | 32 +++++-
drivers/power/supply/axp20x_usb_power.c | 116 ++++++++++++++++++---
8 files changed, 197 insertions(+), 22 deletions(-)
create mode 100644 arch/arm/boot/dts/axp223.dtsi
--
2.9.3
^ permalink raw reply
* [PATCH 1/1] arm64: mm: add config options for page table configuration
From: Will Deacon @ 2016-12-09 10:57 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <dd102973-5737-3168-bffe-7ac5e769aecb@broadcom.com>
On Thu, Dec 08, 2016 at 11:33:39AM -0800, Scott Branden wrote:
> Since I currently have your attention: I do think there is fundamental bug
> in the ARM64 mm implementation. If you look at /sys/devices/system/memory
> it only shows the last memoryX section created after init.
That directory doesn't seem to exist on my arm64 systems. Do I have to
enable something specific in the .config?
Will
^ permalink raw reply
* [RFC, PATCHv1 00/28] 5-level paging
From: Catalin Marinas @ 2016-12-09 10:51 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <13962749.Q2mLWEctkQ@wuerfel>
On Fri, Dec 09, 2016 at 11:24:12AM +0100, Arnd Bergmann wrote:
> On Friday, December 9, 2016 6:01:30 AM CET Ingo Molnar wrote:
> > > - Handle opt-in wider address space for userspace.
> > >
> > > Not all userspace is ready to handle addresses wider than current
> > > 47-bits. At least some JIT compiler make use of upper bits to encode
> > > their info.
> > >
> > > We need to have an interface to opt-in wider addresses from userspace
> > > to avoid regressions.
> > >
> > > For now, I've included testing-only patch which bumps TASK_SIZE to
> > > 56-bits. This can be handy for testing to see what breaks if we max-out
> > > size of virtual address space.
> >
> > So this is just a detail - but it sounds a bit limiting to me to provide an 'opt
> > in' flag for something that will work just fine on the vast majority of 64-bit
> > software.
> >
> > Please make this an opt out compatibility flag instead: similar to how we handle
> > address space layout limitations/quirks ABI details, such as ADDR_LIMIT_32BIT,
> > ADDR_LIMIT_3GB, ADDR_COMPAT_LAYOUT, READ_IMPLIES_EXEC, etc.
>
> We've had a similar discussion about JIT software on ARM64, which has a wide
> range of supported page table layouts and some software wants to limit that
> to a specific number.
>
> I don't remember the outcome of that discussion, but I'm adding a few people
> to Cc that might remember.
The arm64 kernel supports several user VA space configurations (though
commonly 39 and 48-bit) and has had these from the initial port. We
realised that certain JITs (e.g.
https://bugzilla.mozilla.org/show_bug.cgi?id=1143022) and IIRC LLVM
assume a 47-bit user VA but AFAICT, most have been fixed.
ARMv8.1 also supports 52-bit VA (though only with 64K pages and we
haven't added support for it yet). However, it's likely that if we make
a 52-bit TASK_SIZE this the default, we will break some user
assumptions. While arguably that's not necessarily ABI, if user relies
on a 47 or 48-bit VA the kernel shouldn't break it. So I'm strongly
inclined to make the 52-bit TASK_SIZE an opt-in on arm64.
--
Catalin
^ permalink raw reply
* [PATCH] builddeb: Use aarch64 instead of arm64 for UTS_MACHINE
From: Will Deacon @ 2016-12-09 10:50 UTC (permalink / raw)
To: linux-arm-kernel
From: Michal Marek <mmarek@suse.com>
On arm64-based systems, uname -m reports "aarch64", so this is what
we should be using for UTS_MACHINE and matching that in the builddeb
script instead of "arm64".
There was a patch fixing this:
https://patchwork.kernel.org/patch/9305483/
but I accidentally merged v1 of the patch, which omitted the update to
builddeb. This patch follows up with that missing hunk.
Reported-by: Victor Chong <victor.chong@linaro.org>
Signed-off-by: Michal Marek <mmarek@suse.com>
[will: salvaged missing hunk from original patch]
Signed-off-by: Will Deacon <will.deacon@arm.com>
---
scripts/package/builddeb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/package/builddeb b/scripts/package/builddeb
index 8ea9fd2b6573..9530b1634a02 100755
--- a/scripts/package/builddeb
+++ b/scripts/package/builddeb
@@ -51,7 +51,7 @@ set_debarch() {
debarch=hppa ;;
mips*)
debarch=mips$(grep -q CPU_LITTLE_ENDIAN=y $KCONFIG_CONFIG && echo el || true) ;;
- arm64)
+ aarch64*)
debarch=arm64 ;;
arm*)
if grep -q CONFIG_AEABI=y $KCONFIG_CONFIG; then
--
2.1.4
^ permalink raw reply related
* [PATCH] ARM: EXYNOS: move exynos_pm_init into pm.c and remove init_late hook
From: Pankaj Dubey @ 2016-12-09 10:31 UTC (permalink / raw)
To: linux-arm-kernel
We can safely move exynos_pm_init into pm.c as late_initcall and remove
init_late hook from exynos.c. This will remove extern declarations from
common.h and move PM specific operations in pm.c rather being scattered
across many files.
Signed-off-by: Pankaj Dubey <pankaj.dubey@samsung.com>
---
arch/arm/mach-exynos/common.h | 6 ------
arch/arm/mach-exynos/exynos.c | 10 ----------
arch/arm/mach-exynos/suspend.c | 13 ++++++++++---
3 files changed, 10 insertions(+), 19 deletions(-)
diff --git a/arch/arm/mach-exynos/common.h b/arch/arm/mach-exynos/common.h
index fb12d11..cfd55ba 100644
--- a/arch/arm/mach-exynos/common.h
+++ b/arch/arm/mach-exynos/common.h
@@ -134,12 +134,6 @@ void exynos_clear_boot_flag(unsigned int cpu, unsigned int mode);
extern u32 exynos_get_eint_wake_mask(void);
-#ifdef CONFIG_PM_SLEEP
-extern void __init exynos_pm_init(void);
-#else
-static inline void exynos_pm_init(void) {}
-#endif
-
extern void exynos_cpu_resume(void);
extern void exynos_cpu_resume_ns(void);
diff --git a/arch/arm/mach-exynos/exynos.c b/arch/arm/mach-exynos/exynos.c
index fa08ef9..040ea66 100644
--- a/arch/arm/mach-exynos/exynos.c
+++ b/arch/arm/mach-exynos/exynos.c
@@ -58,15 +58,6 @@ void __init exynos_sysram_init(void)
}
}
-static void __init exynos_init_late(void)
-{
- if (of_machine_is_compatible("samsung,exynos5440"))
- /* to be supported later */
- return;
-
- exynos_pm_init();
-}
-
static int __init exynos_fdt_map_chipid(unsigned long node, const char *uname,
int depth, void *data)
{
@@ -216,7 +207,6 @@ DT_MACHINE_START(EXYNOS_DT, "SAMSUNG EXYNOS (Flattened Device Tree)")
.init_early = exynos_firmware_init,
.init_irq = exynos_init_irq,
.init_machine = exynos_dt_machine_init,
- .init_late = exynos_init_late,
.dt_compat = exynos_dt_compat,
.dt_fixup = exynos_dt_fixup,
MACHINE_END
diff --git a/arch/arm/mach-exynos/suspend.c b/arch/arm/mach-exynos/suspend.c
index 73df9f3..f318b08 100644
--- a/arch/arm/mach-exynos/suspend.c
+++ b/arch/arm/mach-exynos/suspend.c
@@ -698,21 +698,25 @@ static const struct of_device_id exynos_pmu_of_device_ids[] __initconst = {
static struct syscore_ops exynos_pm_syscore_ops;
-void __init exynos_pm_init(void)
+static int __init exynos_pm_init(void)
{
const struct of_device_id *match;
struct device_node *np;
u32 tmp;
+ if (of_machine_is_compatible("samsung,exynos5440"))
+ /* to be supported later */
+ return 0;
+
np = of_find_matching_node_and_match(NULL, exynos_pmu_of_device_ids, &match);
if (!np) {
pr_err("Failed to find PMU node\n");
- return;
+ return -ENODEV;
}
if (WARN_ON(!of_find_property(np, "interrupt-controller", NULL))) {
pr_warn("Outdated DT detected, suspend/resume will NOT work\n");
- return;
+ return -ENODEV;
}
pm_data = (const struct exynos_pm_data *) match->data;
@@ -727,4 +731,7 @@ void __init exynos_pm_init(void)
register_syscore_ops(&exynos_pm_syscore_ops);
suspend_set_ops(&exynos_suspend_ops);
+
+ return 0;
}
+late_initcall(exynos_pm_init);
--
2.7.4
^ permalink raw reply related
* [PATCH v3 12/12] arm64: configs: enable SDHCI driver for Xenon
From: Gregory CLEMENT @ 2016-12-09 10:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
This patch enables the driver for the SDHCI controller found on the
Marvell Armada 3700 and 7K/8K ARM64 SoCs.
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index dab2cb0c1f1c..2d1f5ee62b18 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -353,6 +353,7 @@ CONFIG_MMC_DW=y
CONFIG_MMC_DW_EXYNOS=y
CONFIG_MMC_DW_K3=y
CONFIG_MMC_SUNXI=y
+CONFIG_MMC_SDHCI_XENON=y
CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_GPIO=y
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 11/12] arm64: dts: marvell: add sdhci support for Armada 7K/8K
From: Gregory CLEMENT @ 2016-12-09 10:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
Also enable it on the Armada 7040 DB board
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
arch/arm64/boot/dts/marvell/armada-7040-db.dts | 7 +++++++
arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 9 +++++++++
2 files changed, 16 insertions(+)
diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 070b589680c5..f7f978a12a49 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -146,3 +146,10 @@
&cpm_usb3_1 {
status = "okay";
};
+
+&sdhci0 {
+ status = "okay";
+ bus-width = <4>;
+ no-1-8-v;
+ non-removable;
+};
diff --git a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
index 7b6136182ad0..e812fb59fb80 100644
--- a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
@@ -229,6 +229,15 @@
};
+ sdhci0: sdhci at 6e0000 {
+ compatible = "marvell,armada-7000-sdhci";
+ reg = <0x6e0000 0x300>;
+ interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "core";
+ clocks = <&cpm_syscon0 1 4>;
+ status = "disabled";
+ };
+
ap_syscon: system-controller at 6f4000 {
compatible = "marvell,ap806-system-controller",
"syscon";
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 10/12] arm64: dts: marvell: add eMMC support for Armada 37xx
From: Gregory CLEMENT @ 2016-12-09 10:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
Add the eMMC support for Armada 37xx SoC and enable it in the Armada 3720
DB board.
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
arch/arm64/boot/dts/marvell/armada-3720-db.dts | 17 +++++++++++++++++
arch/arm64/boot/dts/marvell/armada-37xx.dtsi | 11 +++++++++++
2 files changed, 28 insertions(+)
diff --git a/arch/arm64/boot/dts/marvell/armada-3720-db.dts b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
index 1372e9a6aaa4..707625031e29 100644
--- a/arch/arm64/boot/dts/marvell/armada-3720-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
@@ -72,6 +72,23 @@
status = "okay";
};
+&sdhci0 {
+ non-removable;
+ bus-width = <8>;
+ mmc-ddr-1_8v;
+ mmc-hs400-1_8v;
+ marvell,xenon-emmc;
+ marvell,pad-type = "fixed-1-8v";
+ status = "okay";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+ mmccard: mmccard at 0 {
+ compatible = "mmc-card";
+ reg = <0>;
+ };
+};
+
/* CON31 */
&usb3 {
status = "okay";
diff --git a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
index c4762538ec01..0c4cafe92e66 100644
--- a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
@@ -161,6 +161,17 @@
};
};
+ sdhci0: sdhci at d8000 {
+ compatible = "marvell,armada-3700-sdhci",
+ "marvell,sdhci-xenon";
+ reg = <0xd8000 0x300
+ 0x17808 0x4>;
+ interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&nb_perih_clk 0>;
+ clock-names = "core";
+ status = "disabled";
+ };
+
sata: sata at e0000 {
compatible = "marvell,armada-3700-ahci";
reg = <0xe0000 0x2000>;
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 09/12] mmc: sdhci-xenon: Add SOC PHY PAD voltage control
From: Gregory CLEMENT @ 2016-12-09 10:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
From: Hu Ziji <huziji@marvell.com>
Some SOCs have PHY PAD outside Xenon IP.
PHY PAD voltage should match signalling voltage in use.
Add generic SOC PHY PAD voltage control interface.
Implement Aramda-3700 SOC PHY PAD voltage control.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
drivers/mmc/host/sdhci-xenon-phy.c | 111 +++++++++++++++++++++++++++++-
drivers/mmc/host/sdhci-xenon.c | 2 +-
drivers/mmc/host/sdhci-xenon.h | 2 +-
3 files changed, 114 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c
index 209c522c5a4b..21b3f834f1d1 100644
--- a/drivers/mmc/host/sdhci-xenon-phy.c
+++ b/drivers/mmc/host/sdhci-xenon-phy.c
@@ -148,6 +148,22 @@ enum phy_type_enum {
NR_PHY_TYPES
};
+struct soc_pad_ctrl_table {
+ const char *soc;
+ void (*set_soc_pad)(struct sdhci_host *host,
+ unsigned char signal_voltage);
+};
+
+struct soc_pad_ctrl {
+ /* Register address of SOC PHY PAD ctrl */
+ void __iomem *reg;
+ /* SOC PHY PAD ctrl type */
+ enum soc_pad_ctrl_type pad_type;
+ /* SOC specific operation to set SOC PHY PAD */
+ void (*set_soc_pad)(struct sdhci_host *host,
+ unsigned char signal_voltage);
+};
+
static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = {
.timing_adj = SDHCI_EMMC_5_0_PHY_TIMING_ADJUST,
.func_ctrl = SDHCI_EMMC_5_0_PHY_FUNC_CONTROL,
@@ -181,6 +197,8 @@ struct emmc_phy_params {
u8 nr_tun_times;
/* Divider for calculating Tuning Step */
u8 tun_step_divider;
+
+ struct soc_pad_ctrl pad_ctrl;
};
static int alloc_emmc_phy(struct sdhci_xenon_priv *priv)
@@ -257,6 +275,45 @@ static int emmc_phy_init(struct sdhci_host *host)
return 0;
}
+#define ARMADA_3700_SOC_PAD_1_8V 0x1
+#define ARMADA_3700_SOC_PAD_3_3V 0x0
+
+static void armada_3700_soc_pad_voltage_set(struct sdhci_host *host,
+ unsigned char signal_voltage)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct emmc_phy_params *params = priv->phy_params;
+
+ if (params->pad_ctrl.pad_type == SOC_PAD_FIXED_1_8V) {
+ writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
+ } else if (params->pad_ctrl.pad_type == SOC_PAD_SD) {
+ if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
+ writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
+ else if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
+ writel(ARMADA_3700_SOC_PAD_3_3V, params->pad_ctrl.reg);
+ }
+}
+
+/*
+ * Set SOC PHY voltage PAD control register,
+ * according to the operation voltage on PAD.
+ * The detailed operation depends on SOC implementaion.
+ */
+static void emmc_phy_set_soc_pad(struct sdhci_host *host,
+ unsigned char signal_voltage)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct emmc_phy_params *params = priv->phy_params;
+
+ if (!params->pad_ctrl.reg)
+ return;
+
+ if (params->pad_ctrl.set_soc_pad)
+ params->pad_ctrl.set_soc_pad(host, signal_voltage);
+}
+
/*
* Enable eMMC PHY HW DLL
* DLL should be enabled and stable before HS200/SDR104 tuning,
@@ -630,6 +687,51 @@ static void emmc_phy_set(struct sdhci_host *host,
dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n");
}
+static int get_dt_pad_ctrl_data(struct sdhci_host *host,
+ struct device_node *np,
+ struct emmc_phy_params *params)
+{
+ int ret = 0;
+ const char *name;
+ struct resource iomem;
+
+ if (of_device_is_compatible(np, "marvell,armada-3700-sdhci"))
+ params->pad_ctrl.set_soc_pad = armada_3700_soc_pad_voltage_set;
+ else
+ return 0;
+
+ if (of_address_to_resource(np, 1, &iomem)) {
+ dev_err(mmc_dev(host->mmc), "Unable to find SOC PAD ctrl register address for %s\n",
+ np->name);
+ return -EINVAL;
+ }
+
+ params->pad_ctrl.reg = devm_ioremap_resource(mmc_dev(host->mmc),
+ &iomem);
+ if (IS_ERR(params->pad_ctrl.reg)) {
+ dev_err(mmc_dev(host->mmc), "Unable to get SOC PHY PAD ctrl regiser for %s\n",
+ np->name);
+ return PTR_ERR(params->pad_ctrl.reg);
+ }
+
+ ret = of_property_read_string(np, "marvell,pad-type", &name);
+ if (ret) {
+ dev_err(mmc_dev(host->mmc), "Unable to determine SOC PHY PAD ctrl type\n");
+ return ret;
+ }
+ if (!strcmp(name, "sd")) {
+ params->pad_ctrl.pad_type = SOC_PAD_SD;
+ } else if (!strcmp(name, "fixed-1-8v")) {
+ params->pad_ctrl.pad_type = SOC_PAD_FIXED_1_8V;
+ } else {
+ dev_err(mmc_dev(host->mmc), "Unsupported SOC PHY PAD ctrl type %s\n",
+ name);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
static int emmc_phy_parse_param_dt(struct sdhci_host *host,
struct device_node *np,
struct emmc_phy_params *params)
@@ -663,7 +765,14 @@ static int emmc_phy_parse_param_dt(struct sdhci_host *host,
else
params->tun_step_divider = SDHCI_TUNING_STEP_DIVIDER;
- return 0;
+ return get_dt_pad_ctrl_data(host, np, params);
+}
+
+/* Set SOC PHY Voltage PAD */
+void xenon_soc_pad_ctrl(struct sdhci_host *host,
+ unsigned char signal_voltage)
+{
+ emmc_phy_set_soc_pad(host, signal_voltage);
}
/*
diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
index 2e47fef490be..21abacf2e9b8 100644
--- a/drivers/mmc/host/sdhci-xenon.c
+++ b/drivers/mmc/host/sdhci-xenon.c
@@ -322,6 +322,8 @@ static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
*/
enable_xenon_internal_clk(host);
+ xenon_soc_pad_ctrl(host, ios->signal_voltage);
+
if (priv->init_card_type == MMC_TYPE_MMC)
return xenon_emmc_signal_voltage_switch(mmc, ios);
diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
index 86b5d2b1f1aa..a8312e36a03c 100644
--- a/drivers/mmc/host/sdhci-xenon.h
+++ b/drivers/mmc/host/sdhci-xenon.h
@@ -106,4 +106,6 @@ struct sdhci_xenon_priv {
int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios);
int xenon_phy_parse_dt(struct device_node *np,
struct sdhci_host *host);
+void xenon_soc_pad_ctrl(struct sdhci_host *host,
+ unsigned char signal_voltage);
#endif
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 08/12] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC.
From: Gregory CLEMENT @ 2016-12-09 10:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
From: Hu Ziji <huziji@marvell.com>
Marvell Xenon eMMC/SD/SDIO Host Controller contains PHY.
Multiple types of PHYs are supported.
Add support to multiple types of PHYs init and configuration.
Add register definitions of PHYs.
Xenon PHY cannot fit in kernel common PHY framework.
Xenon SDHC PHY register is a part of Xenon SDHC register set.
Besides, MMC initialization has to call several PHY functions
to complete timing setting.
Those PHY setting functions have to access SDHC registers
and know current MMC setting, such as bus width, clock frequency
and speed mode.
As a result, implement Xenon PHY in MMC host directory.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
drivers/mmc/host/Makefile | 2 +-
drivers/mmc/host/sdhci-xenon-phy.c | 799 ++++++++++++++++++++++++++++++-
drivers/mmc/host/sdhci-xenon.c | 3 +-
drivers/mmc/host/sdhci-xenon.h | 39 +-
4 files changed, 841 insertions(+), 2 deletions(-)
create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 75eaf743486c..4f2854556ff7 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -82,4 +82,4 @@ ifeq ($(CONFIG_CB710_DEBUG),y)
endif
obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o
-sdhci-xenon-driver-y += sdhci-xenon.o
+sdhci-xenon-driver-y += sdhci-xenon.o sdhci-xenon-phy.o
diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c
new file mode 100644
index 000000000000..209c522c5a4b
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon-phy.c
@@ -0,0 +1,799 @@
+/*
+ * PHY support for Xenon SDHC
+ *
+ * Copyright (C) 2016 Marvell, All Rights Reserved.
+ *
+ * Author: Hu Ziji <huziji@marvell.com>
+ * Date: 2016-8-24
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ */
+
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/of_address.h>
+
+#include "sdhci-pltfm.h"
+#include "sdhci-xenon.h"
+
+/* Register base for eMMC PHY 5.0 Version */
+#define SDHCI_EMMC_5_0_PHY_REG_BASE 0x0160
+/* Register base for eMMC PHY 5.1 Version */
+#define SDHCI_EMMC_PHY_REG_BASE 0x0170
+
+#define SDHCI_EMMC_PHY_TIMING_ADJUST SDHCI_EMMC_PHY_REG_BASE
+#define SDHCI_EMMC_5_0_PHY_TIMING_ADJUST SDHCI_EMMC_5_0_PHY_REG_BASE
+#define SDHCI_TIMING_ADJUST_SLOW_MODE BIT(29)
+#define SDHCI_TIMING_ADJUST_SDIO_MODE BIT(28)
+#define SDHCI_OUTPUT_QSN_PHASE_SELECT BIT(17)
+#define SDHCI_SAMPL_INV_QSP_PHASE_SELECT BIT(18)
+#define SDHCI_SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18
+#define SDHCI_PHY_INITIALIZAION BIT(31)
+#define SDHCI_WAIT_CYCLE_BEFORE_USING_MASK 0xF
+#define SDHCI_WAIT_CYCLE_BEFORE_USING_SHIFT 12
+#define SDHCI_FC_SYNC_EN_DURATION_MASK 0xF
+#define SDHCI_FC_SYNC_EN_DURATION_SHIFT 8
+#define SDHCI_FC_SYNC_RST_EN_DURATION_MASK 0xF
+#define SDHCI_FC_SYNC_RST_EN_DURATION_SHIFT 4
+#define SDHCI_FC_SYNC_RST_DURATION_MASK 0xF
+#define SDHCI_FC_SYNC_RST_DURATION_SHIFT 0
+
+#define SDHCI_EMMC_PHY_FUNC_CONTROL (SDHCI_EMMC_PHY_REG_BASE + 0x4)
+#define SDHCI_EMMC_5_0_PHY_FUNC_CONTROL \
+ (SDHCI_EMMC_5_0_PHY_REG_BASE + 0x4)
+#define SDHCI_ASYNC_DDRMODE_MASK BIT(23)
+#define SDHCI_ASYNC_DDRMODE_SHIFT 23
+#define SDHCI_CMD_DDR_MODE BIT(16)
+#define SDHCI_DQ_DDR_MODE_SHIFT 8
+#define SDHCI_DQ_DDR_MODE_MASK 0xFF
+#define SDHCI_DQ_ASYNC_MODE BIT(4)
+
+#define SDHCI_EMMC_PHY_PAD_CONTROL (SDHCI_EMMC_PHY_REG_BASE + 0x8)
+#define SDHCI_EMMC_5_0_PHY_PAD_CONTROL \
+ (SDHCI_EMMC_5_0_PHY_REG_BASE + 0x8)
+#define SDHCI_REC_EN_SHIFT 24
+#define SDHCI_REC_EN_MASK 0xF
+#define SDHCI_FC_DQ_RECEN BIT(24)
+#define SDHCI_FC_CMD_RECEN BIT(25)
+#define SDHCI_FC_QSP_RECEN BIT(26)
+#define SDHCI_FC_QSN_RECEN BIT(27)
+#define SDHCI_OEN_QSN BIT(28)
+#define SDHCI_AUTO_RECEN_CTRL BIT(30)
+#define SDHCI_FC_ALL_CMOS_RECEIVER 0xF000
+
+#define SDHCI_EMMC5_FC_QSP_PD BIT(18)
+#define SDHCI_EMMC5_FC_QSP_PU BIT(22)
+#define SDHCI_EMMC5_FC_CMD_PD BIT(17)
+#define SDHCI_EMMC5_FC_CMD_PU BIT(21)
+#define SDHCI_EMMC5_FC_DQ_PD BIT(16)
+#define SDHCI_EMMC5_FC_DQ_PU BIT(20)
+
+#define SDHCI_EMMC_PHY_PAD_CONTROL1 (SDHCI_EMMC_PHY_REG_BASE + 0xC)
+#define SDHCI_EMMC5_1_FC_QSP_PD BIT(9)
+#define SDHCI_EMMC5_1_FC_QSP_PU BIT(25)
+#define SDHCI_EMMC5_1_FC_CMD_PD BIT(8)
+#define SDHCI_EMMC5_1_FC_CMD_PU BIT(24)
+#define SDHCI_EMMC5_1_FC_DQ_PD 0xFF
+#define SDHCI_EMMC5_1_FC_DQ_PU (0xFF << 16)
+
+#define SDHCI_EMMC_PHY_PAD_CONTROL2 (SDHCI_EMMC_PHY_REG_BASE + 0x10)
+#define SDHCI_EMMC_5_0_PHY_PAD_CONTROL2 \
+ (SDHCI_EMMC_5_0_PHY_REG_BASE + 0xC)
+#define SDHCI_ZNR_MASK 0x1F
+#define SDHCI_ZNR_SHIFT 8
+#define SDHCI_ZPR_MASK 0x1F
+/* Perferred ZNR and ZPR value vary between different boards.
+ * The specific ZNR and ZPR value should be defined here
+ * according to board actual timing.
+ */
+#define SDHCI_ZNR_DEF_VALUE 0xF
+#define SDHCI_ZPR_DEF_VALUE 0xF
+
+#define SDHCI_EMMC_PHY_DLL_CONTROL (SDHCI_EMMC_PHY_REG_BASE + 0x14)
+#define SDHCI_EMMC_5_0_PHY_DLL_CONTROL \
+ (SDHCI_EMMC_5_0_PHY_REG_BASE + 0x10)
+#define SDHCI_DLL_ENABLE BIT(31)
+#define SDHCI_DLL_UPDATE_STROBE_5_0 BIT(30)
+#define SDHCI_DLL_REFCLK_SEL BIT(30)
+#define SDHCI_DLL_UPDATE BIT(23)
+#define SDHCI_DLL_PHSEL1_SHIFT 24
+#define SDHCI_DLL_PHSEL0_SHIFT 16
+#define SDHCI_DLL_PHASE_MASK 0x3F
+#define SDHCI_DLL_PHASE_90_DEGREE 0x1F
+#define SDHCI_DLL_FAST_LOCK BIT(5)
+#define SDHCI_DLL_GAIN2X BIT(3)
+#define SDHCI_DLL_BYPASS_EN BIT(0)
+
+#define SDHCI_EMMC_5_0_PHY_LOGIC_TIMING_ADJUST \
+ (SDHCI_EMMC_5_0_PHY_REG_BASE + 0x14)
+#define SDHCI_EMMC_PHY_LOGIC_TIMING_ADJUST (SDHCI_EMMC_PHY_REG_BASE + 0x18)
+#define SDHCI_LOGIC_TIMING_VALUE 0x00AA8977
+
+enum soc_pad_ctrl_type {
+ SOC_PAD_SD,
+ SOC_PAD_FIXED_1_8V,
+};
+
+/*
+ * List offset of PHY registers and some special register values
+ * in eMMC PHY 5.0 or eMMC PHY 5.1
+ */
+struct xenon_emmc_phy_regs {
+ /* Offset of Timing Adjust register */
+ u16 timing_adj;
+ /* Offset of Func Control register */
+ u16 func_ctrl;
+ /* Offset of Pad Control register */
+ u16 pad_ctrl;
+ /* Offset of Pad Control register 2 */
+ u16 pad_ctrl2;
+ /* Offset of DLL Control register */
+ u16 dll_ctrl;
+ /* Offset of Logic Timing Adjust register */
+ u16 logic_timing_adj;
+ /* DLL Update Enable bit */
+ u32 dll_update;
+};
+
+static const char * const phy_types[] = {
+ "emmc 5.0 phy",
+ "emmc 5.1 phy"
+};
+
+enum phy_type_enum {
+ EMMC_5_0_PHY,
+ EMMC_5_1_PHY,
+ NR_PHY_TYPES
+};
+
+static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = {
+ .timing_adj = SDHCI_EMMC_5_0_PHY_TIMING_ADJUST,
+ .func_ctrl = SDHCI_EMMC_5_0_PHY_FUNC_CONTROL,
+ .pad_ctrl = SDHCI_EMMC_5_0_PHY_PAD_CONTROL,
+ .pad_ctrl2 = SDHCI_EMMC_5_0_PHY_PAD_CONTROL2,
+ .dll_ctrl = SDHCI_EMMC_5_0_PHY_DLL_CONTROL,
+ .logic_timing_adj = SDHCI_EMMC_5_0_PHY_LOGIC_TIMING_ADJUST,
+ .dll_update = SDHCI_DLL_UPDATE_STROBE_5_0,
+};
+
+static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = {
+ .timing_adj = SDHCI_EMMC_PHY_TIMING_ADJUST,
+ .func_ctrl = SDHCI_EMMC_PHY_FUNC_CONTROL,
+ .pad_ctrl = SDHCI_EMMC_PHY_PAD_CONTROL,
+ .pad_ctrl2 = SDHCI_EMMC_PHY_PAD_CONTROL2,
+ .dll_ctrl = SDHCI_EMMC_PHY_DLL_CONTROL,
+ .logic_timing_adj = SDHCI_EMMC_PHY_LOGIC_TIMING_ADJUST,
+ .dll_update = SDHCI_DLL_UPDATE,
+};
+
+/*
+ * eMMC PHY configuration and operations
+ */
+struct emmc_phy_params {
+ bool slow_mode;
+
+ u8 znr;
+ u8 zpr;
+
+ /* Nr of consecutive Sampling Points of a Valid Sampling Window */
+ u8 nr_tun_times;
+ /* Divider for calculating Tuning Step */
+ u8 tun_step_divider;
+};
+
+static int alloc_emmc_phy(struct sdhci_xenon_priv *priv)
+{
+ struct emmc_phy_params *params;
+
+ params = kzalloc(sizeof(*params), GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ priv->phy_params = params;
+ if (priv->phy_type == EMMC_5_0_PHY)
+ priv->emmc_phy_regs = &xenon_emmc_5_0_phy_regs;
+ else
+ priv->emmc_phy_regs = &xenon_emmc_5_1_phy_regs;
+
+ return 0;
+}
+
+/*
+ * eMMC 5.0/5.1 PHY init/re-init.
+ * eMMC PHY init should be executed after:
+ * 1. SDCLK frequecny changes.
+ * 2. SDCLK is stopped and re-enabled.
+ * 3. config in emmc_phy_regs->timing_adj and emmc_phy_regs->func_ctrl
+ * are changed
+ */
+static int emmc_phy_init(struct sdhci_host *host)
+{
+ u32 reg;
+ u32 wait, clock;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
+
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ reg |= SDHCI_PHY_INITIALIZAION;
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+
+ /* Add duration of FC_SYNC_RST */
+ wait = ((reg >> SDHCI_FC_SYNC_RST_DURATION_SHIFT) &
+ SDHCI_FC_SYNC_RST_DURATION_MASK);
+ /* Add interval between FC_SYNC_EN and FC_SYNC_RST */
+ wait += ((reg >> SDHCI_FC_SYNC_RST_EN_DURATION_SHIFT) &
+ SDHCI_FC_SYNC_RST_EN_DURATION_MASK);
+ /* Add duration of asserting FC_SYNC_EN */
+ wait += ((reg >> SDHCI_FC_SYNC_EN_DURATION_SHIFT) &
+ SDHCI_FC_SYNC_EN_DURATION_MASK);
+ /* Add duration of waiting for PHY */
+ wait += ((reg >> SDHCI_WAIT_CYCLE_BEFORE_USING_SHIFT) &
+ SDHCI_WAIT_CYCLE_BEFORE_USING_MASK);
+ /* 4 addtional bus clock and 4 AXI bus clock are required */
+ wait += 8;
+ wait <<= 20;
+
+ clock = host->clock;
+ if (!clock)
+ /* Use the possibly slowest bus frequency value */
+ clock = SDHCI_LOWEST_SDCLK_FREQ;
+ /* get the wait time */
+ wait /= clock;
+ wait++;
+ /* wait for host eMMC PHY init completes */
+ udelay(wait);
+
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ reg &= SDHCI_PHY_INITIALIZAION;
+ if (reg) {
+ dev_err(mmc_dev(host->mmc), "eMMC PHY init cannot complete after %d us\n",
+ wait);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/*
+ * Enable eMMC PHY HW DLL
+ * DLL should be enabled and stable before HS200/SDR104 tuning,
+ * and before HS400 data strobe setting.
+ */
+static int emmc_phy_enable_dll(struct sdhci_host *host)
+{
+ u32 reg;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
+ u8 timeout;
+
+ if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
+ return -EINVAL;
+
+ reg = sdhci_readl(host, phy_regs->dll_ctrl);
+ if (reg & SDHCI_DLL_ENABLE)
+ return 0;
+
+ /* Enable DLL */
+ reg = sdhci_readl(host, phy_regs->dll_ctrl);
+ reg |= (SDHCI_DLL_ENABLE | SDHCI_DLL_FAST_LOCK);
+
+ /*
+ * Set Phase as 90 degree, which is most common value.
+ * Might set another value if necessary.
+ * The granularity is 1 degree.
+ */
+ reg &= ~((SDHCI_DLL_PHASE_MASK << SDHCI_DLL_PHSEL0_SHIFT) |
+ (SDHCI_DLL_PHASE_MASK << SDHCI_DLL_PHSEL1_SHIFT));
+ reg |= ((SDHCI_DLL_PHASE_90_DEGREE << SDHCI_DLL_PHSEL0_SHIFT) |
+ (SDHCI_DLL_PHASE_90_DEGREE << SDHCI_DLL_PHSEL1_SHIFT));
+
+ reg &= ~SDHCI_DLL_BYPASS_EN;
+ reg |= phy_regs->dll_update;
+ if (priv->phy_type == EMMC_5_1_PHY)
+ reg &= ~SDHCI_DLL_REFCLK_SEL;
+ sdhci_writel(host, reg, phy_regs->dll_ctrl);
+
+ /* Wait max 32 ms */
+ timeout = 32;
+ while (!(sdhci_readw(host, SDHCI_SLOT_EXT_PRESENT_STATE) &
+ SDHCI_DLL_LOCK_STATE)) {
+ if (!timeout) {
+ dev_err(mmc_dev(host->mmc), "Wait for DLL Lock time-out\n");
+ return -ETIMEDOUT;
+ }
+ timeout--;
+ mdelay(1);
+ }
+ return 0;
+}
+
+/*
+ * Config to eMMC PHY to prepare for tuning.
+ * Enable HW DLL and set the TUNING_STEP
+ */
+static int emmc_phy_config_tuning(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct emmc_phy_params *params = priv->phy_params;
+ u32 reg, tuning_step;
+ int ret;
+ unsigned long flags;
+
+ if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
+ return -EINVAL;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ ret = emmc_phy_enable_dll(host);
+ if (ret) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return ret;
+ }
+
+ /* Achieve TUNGING_STEP with HW DLL help */
+ reg = sdhci_readl(host, SDHCI_SLOT_DLL_CUR_DLY_VAL);
+ tuning_step = reg / params->tun_step_divider;
+ if (unlikely(tuning_step > SDHCI_TUNING_STEP_MASK)) {
+ dev_warn(mmc_dev(host->mmc),
+ "HS200 TUNING_STEP %d is larger than MAX value\n",
+ tuning_step);
+ tuning_step = SDHCI_TUNING_STEP_MASK;
+ }
+
+ /* Set TUNING_STEP for later tuning */
+ reg = sdhci_readl(host, SDHCI_SLOT_OP_STATUS_CTRL);
+ reg &= ~(SDHCI_TUN_CONSECUTIVE_TIMES_MASK <<
+ SDHCI_TUN_CONSECUTIVE_TIMES_SHIFT);
+ reg |= (params->nr_tun_times << SDHCI_TUN_CONSECUTIVE_TIMES_SHIFT);
+ reg &= ~(SDHCI_TUNING_STEP_MASK << SDHCI_TUNING_STEP_SHIFT);
+ reg |= (tuning_step << SDHCI_TUNING_STEP_SHIFT);
+ sdhci_writel(host, reg, SDHCI_SLOT_OP_STATUS_CTRL);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+ return 0;
+}
+
+static void __emmc_phy_disable_data_strobe(struct sdhci_host *host)
+{
+ u32 reg;
+
+ /* Disable SDHC Data Strobe */
+ reg = sdhci_readl(host, SDHCI_SLOT_EMMC_CTRL);
+ reg &= ~SDHCI_ENABLE_DATA_STROBE;
+ sdhci_writel(host, reg, SDHCI_SLOT_EMMC_CTRL);
+}
+
+/* Set HS400 Data Strobe */
+static void emmc_phy_strobe_delay_adj(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ unsigned long flags;
+ u32 reg;
+
+ if (WARN_ON(host->timing != MMC_TIMING_MMC_HS400))
+ return;
+
+ if (host->clock <= MMC_HIGH_52_MAX_DTR)
+ return;
+
+ dev_dbg(mmc_dev(host->mmc), "starts HS400 strobe delay adjustment\n");
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ emmc_phy_enable_dll(host);
+
+ /* Enable SDHC Data Strobe */
+ reg = sdhci_readl(host, SDHCI_SLOT_EMMC_CTRL);
+ reg |= SDHCI_ENABLE_DATA_STROBE;
+ sdhci_writel(host, reg, SDHCI_SLOT_EMMC_CTRL);
+
+ /* Set Data Strobe Pull down */
+ if (priv->phy_type == EMMC_5_0_PHY) {
+ reg = sdhci_readl(host, SDHCI_EMMC_5_0_PHY_PAD_CONTROL);
+ reg |= SDHCI_EMMC5_FC_QSP_PD;
+ reg &= ~SDHCI_EMMC5_FC_QSP_PU;
+ sdhci_writel(host, reg, SDHCI_EMMC_5_0_PHY_PAD_CONTROL);
+ } else {
+ reg = sdhci_readl(host, SDHCI_EMMC_PHY_PAD_CONTROL1);
+ reg |= SDHCI_EMMC5_1_FC_QSP_PD;
+ reg &= ~SDHCI_EMMC5_1_FC_QSP_PU;
+ sdhci_writel(host, reg, SDHCI_EMMC_PHY_PAD_CONTROL1);
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static inline bool temp_stage_hs200_to_hs400(struct sdhci_host *host,
+ struct sdhci_xenon_priv *priv)
+{
+ /*
+ * Tmep stages from HS200 to HS400
+ * from HS200 to HS in 200MHz
+ * from 200MHz to 52MHz
+ */
+ if (((priv->timing == MMC_TIMING_MMC_HS200) &&
+ (host->timing == MMC_TIMING_MMC_HS)) ||
+ ((host->timing == MMC_TIMING_MMC_HS) &&
+ (priv->clock > host->clock)))
+ return true;
+
+ return false;
+}
+
+static inline bool temp_stage_hs400_to_h200(struct sdhci_host *host,
+ struct sdhci_xenon_priv *priv)
+{
+ /*
+ * Temp stages from HS400 t0 HS200:
+ * from 200MHz to 52MHz in HS400
+ * from HS400 to HS DDR in 52MHz
+ * from HS DDR to HS in 52MHz
+ * from HS to HS200 in 52MHz
+ */
+ if (((priv->timing == MMC_TIMING_MMC_HS400) &&
+ ((host->clock == MMC_HIGH_52_MAX_DTR) ||
+ (host->timing == MMC_TIMING_MMC_DDR52))) ||
+ ((priv->timing == MMC_TIMING_MMC_DDR52) &&
+ (host->timing == MMC_TIMING_MMC_HS)) ||
+ ((host->timing == MMC_TIMING_MMC_HS200) &&
+ (host->clock == MMC_HIGH_52_MAX_DTR)))
+ return true;
+
+ return false;
+}
+
+/*
+ * If eMMC PHY Slow Mode is required in lower speed mode in SDR mode
+ * (SDLCK < 55MHz), enable Slow Mode to bypass eMMC PHY.
+ * SDIO slower SDR mode also requires Slow Mode.
+ *
+ * If Slow Mode is enabled, return true.
+ * Otherwise, return false.
+ */
+static bool emmc_phy_slow_mode(struct sdhci_host *host,
+ unsigned char timing)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct emmc_phy_params *params = priv->phy_params;
+ struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
+ u32 reg;
+
+ /* Skip temp stages from HS200 to HS400 */
+ if (temp_stage_hs200_to_hs400(host, priv))
+ return 0;
+
+ /* Skip temp stages from HS400 t0 HS200 */
+ if (temp_stage_hs400_to_h200(host, priv))
+ return 0;
+
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ /* Enable Slow Mode for SDIO in slower SDR mode */
+ if ((priv->init_card_type == MMC_TYPE_SDIO) &&
+ ((timing == MMC_TIMING_UHS_SDR25) ||
+ (timing == MMC_TIMING_UHS_SDR12) ||
+ (timing == MMC_TIMING_SD_HS) ||
+ (timing == MMC_TIMING_LEGACY))) {
+ reg |= SDHCI_TIMING_ADJUST_SLOW_MODE;
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+ return true;
+ }
+
+ /* Check if Slow Mode is required in lower speed mode in SDR mode */
+ if (((timing == MMC_TIMING_UHS_SDR50) ||
+ (timing == MMC_TIMING_UHS_SDR25) ||
+ (timing == MMC_TIMING_UHS_SDR12) ||
+ (timing == MMC_TIMING_SD_HS) ||
+ (timing == MMC_TIMING_MMC_HS) ||
+ (timing == MMC_TIMING_LEGACY)) && params->slow_mode) {
+ reg |= SDHCI_TIMING_ADJUST_SLOW_MODE;
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+ return true;
+ }
+
+ reg &= ~SDHCI_TIMING_ADJUST_SLOW_MODE;
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+ return false;
+}
+
+/*
+ * Set-up eMMC 5.0/5.1 PHY.
+ * Specific onfiguration depends on the current speed mode in use.
+ */
+static void emmc_phy_set(struct sdhci_host *host,
+ unsigned char timing)
+{
+ u32 reg;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct emmc_phy_params *params = priv->phy_params;
+ struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
+ unsigned long flags;
+
+ dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting starts\n");
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ reg = sdhci_readl(host, SDHCI_SYS_EXT_OP_CTRL);
+ reg |= SDHCI_MASK_CMD_CONFLICT_ERROR;
+ sdhci_writel(host, reg, SDHCI_SYS_EXT_OP_CTRL);
+
+ /* Setup pad, set bit[28] and bits[26:24] */
+ reg = sdhci_readl(host, phy_regs->pad_ctrl);
+ reg |= (SDHCI_FC_DQ_RECEN | SDHCI_FC_CMD_RECEN |
+ SDHCI_FC_QSP_RECEN | SDHCI_OEN_QSN);
+ /* All FC_XX_RECEIVCE should be set as CMOS Type */
+ reg |= SDHCI_FC_ALL_CMOS_RECEIVER;
+ sdhci_writel(host, reg, phy_regs->pad_ctrl);
+
+ /* Set CMD and DQ Pull Up */
+ if (priv->phy_type == EMMC_5_0_PHY) {
+ reg = sdhci_readl(host, SDHCI_EMMC_5_0_PHY_PAD_CONTROL);
+ reg |= (SDHCI_EMMC5_FC_CMD_PU | SDHCI_EMMC5_FC_DQ_PU);
+ reg &= ~(SDHCI_EMMC5_FC_CMD_PD | SDHCI_EMMC5_FC_DQ_PD);
+ sdhci_writel(host, reg, SDHCI_EMMC_5_0_PHY_PAD_CONTROL);
+ } else {
+ reg = sdhci_readl(host, SDHCI_EMMC_PHY_PAD_CONTROL1);
+ reg |= (SDHCI_EMMC5_1_FC_CMD_PU | SDHCI_EMMC5_1_FC_DQ_PU);
+ reg &= ~(SDHCI_EMMC5_1_FC_CMD_PD | SDHCI_EMMC5_1_FC_DQ_PD);
+ sdhci_writel(host, reg, SDHCI_EMMC_PHY_PAD_CONTROL1);
+ }
+
+ if (timing == MMC_TIMING_LEGACY)
+ goto phy_init;
+
+ /*
+ * FIXME: should depends on the specific board timing.
+ */
+ if ((timing == MMC_TIMING_MMC_HS400) ||
+ (timing == MMC_TIMING_MMC_HS200) ||
+ (timing == MMC_TIMING_UHS_SDR50) ||
+ (timing == MMC_TIMING_UHS_SDR104) ||
+ (timing == MMC_TIMING_UHS_DDR50) ||
+ (timing == MMC_TIMING_UHS_SDR25) ||
+ (timing == MMC_TIMING_MMC_DDR52)) {
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ reg &= ~SDHCI_OUTPUT_QSN_PHASE_SELECT;
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+ }
+
+ /*
+ * If SDIO card, set SDIO Mode
+ * Otherwise, clear SDIO Mode
+ */
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ if (priv->init_card_type == MMC_TYPE_SDIO)
+ reg |= SDHCI_TIMING_ADJUST_SDIO_MODE;
+ else
+ reg &= ~SDHCI_TIMING_ADJUST_SDIO_MODE;
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+
+ if (emmc_phy_slow_mode(host, timing))
+ goto phy_init;
+
+ /*
+ * Set preferred ZNR and ZPR value
+ * The ZNR and ZPR value vary between different boards.
+ * Define them both in sdhci-xenon-emmc-phy.h.
+ */
+ reg = sdhci_readl(host, phy_regs->pad_ctrl2);
+ reg &= ~((SDHCI_ZNR_MASK << SDHCI_ZNR_SHIFT) | SDHCI_ZPR_MASK);
+ reg |= ((params->znr << SDHCI_ZNR_SHIFT) | params->zpr);
+ sdhci_writel(host, reg, phy_regs->pad_ctrl2);
+
+ /*
+ * When setting EMMC_PHY_FUNC_CONTROL register,
+ * SD clock should be disabled
+ */
+ reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+ reg &= ~SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
+
+ reg = sdhci_readl(host, phy_regs->func_ctrl);
+ if ((timing == MMC_TIMING_UHS_DDR50) ||
+ (timing == MMC_TIMING_MMC_HS400) ||
+ (timing == MMC_TIMING_MMC_DDR52))
+ reg |= (SDHCI_DQ_DDR_MODE_MASK << SDHCI_DQ_DDR_MODE_SHIFT) |
+ SDHCI_CMD_DDR_MODE;
+ else
+ reg &= ~((SDHCI_DQ_DDR_MODE_MASK << SDHCI_DQ_DDR_MODE_SHIFT) |
+ SDHCI_CMD_DDR_MODE);
+
+ if (timing == MMC_TIMING_MMC_HS400)
+ reg &= ~SDHCI_DQ_ASYNC_MODE;
+ else
+ reg |= SDHCI_DQ_ASYNC_MODE;
+ sdhci_writel(host, reg, phy_regs->func_ctrl);
+
+ /* Enable bus clock */
+ reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+ reg |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
+
+ if (timing == MMC_TIMING_MMC_HS400)
+ /* Hardware team recommend a value for HS400 */
+ sdhci_writel(host, SDHCI_LOGIC_TIMING_VALUE,
+ phy_regs->logic_timing_adj);
+ else
+ __emmc_phy_disable_data_strobe(host);
+
+phy_init:
+ emmc_phy_init(host);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n");
+}
+
+static int emmc_phy_parse_param_dt(struct sdhci_host *host,
+ struct device_node *np,
+ struct emmc_phy_params *params)
+{
+ u32 value;
+
+ if (of_property_read_bool(np, "marvell,xenon-phy-slow-mode"))
+ params->slow_mode = true;
+ else
+ params->slow_mode = false;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-znr", &value))
+ params->znr = value & SDHCI_ZNR_MASK;
+ else
+ params->znr = SDHCI_ZNR_DEF_VALUE;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-zpr", &value))
+ params->zpr = value & SDHCI_ZPR_MASK;
+ else
+ params->zpr = SDHCI_ZPR_DEF_VALUE;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-nr-success-tun",
+ &value))
+ params->nr_tun_times = value & SDHCI_TUN_CONSECUTIVE_TIMES_MASK;
+ else
+ params->nr_tun_times = SDHCI_TUN_CONSECUTIVE_TIMES;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-tun-step-divider",
+ &value))
+ params->tun_step_divider = value & 0xFF;
+ else
+ params->tun_step_divider = SDHCI_TUNING_STEP_DIVIDER;
+
+ return 0;
+}
+
+/*
+ * Setting PHY when card is working in High Speed Mode.
+ * HS400 set data strobe line.
+ * HS200/SDR104 set tuning config to prepare for tuning.
+ */
+static int xenon_hs_delay_adj(struct sdhci_host *host)
+{
+ int ret = 0;
+
+ if (WARN_ON(host->clock <= SDHCI_DEFAULT_SDCLK_FREQ))
+ return -EINVAL;
+
+ if (host->timing == MMC_TIMING_MMC_HS400) {
+ emmc_phy_strobe_delay_adj(host);
+ return 0;
+ }
+
+ if ((host->timing == MMC_TIMING_MMC_HS200) ||
+ (host->timing == MMC_TIMING_UHS_SDR104)) {
+ ret = emmc_phy_config_tuning(host);
+ if (!ret)
+ return 0;
+ }
+
+ /*
+ * DDR Mode requires driver to scan Sampling Fixed Delay Line,
+ * to find out a perfect operation sampling point.
+ * It is hard to implement such a scan in host driver since initiating
+ * commands by host driver is not safe.
+ * Thus so far just keep PHY Sampling Fixed Delay in default value
+ * in DDR mode.
+ *
+ * If any timing issue occrus in DDR mode on Marvell products,
+ * please contact maintainer to ask for internal support in Marvell.
+ */
+ if ((host->timing == MMC_TIMING_MMC_DDR52) ||
+ (host->timing == MMC_TIMING_UHS_DDR50))
+ dev_warn(mmc_dev(host->mmc), "Timing issue might occur in DDR mode\n");
+ return ret;
+}
+
+/*
+ * Adjust PHY setting.
+ * PHY setting should be adjusted when SDCLK frequency, Bus Width
+ * or Speed Mode is changed.
+ * Addtional config are required when card is working in High Speed mode,
+ * after leaving Legacy Mode.
+ */
+int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ int ret = 0;
+
+ if (!host->clock) {
+ priv->clock = 0;
+ return 0;
+ }
+
+ /*
+ * The timing, frequency or bus width is changed,
+ * better to set eMMC PHY based on current setting
+ * and adjust Xenon SDHC delay.
+ */
+ if ((host->clock == priv->clock) &&
+ (ios->bus_width == priv->bus_width) &&
+ (ios->timing == priv->timing))
+ return 0;
+
+ emmc_phy_set(host, ios->timing);
+
+ /* Update the record */
+ priv->bus_width = ios->bus_width;
+
+ /* Skip temp stages from HS200 to HS400 */
+ if (temp_stage_hs200_to_hs400(host, priv))
+ return 0;
+
+ /* Skip temp stages from HS400 t0 HS200 */
+ if (temp_stage_hs400_to_h200(host, priv))
+ return 0;
+
+ priv->timing = ios->timing;
+ priv->clock = host->clock;
+
+ /* Legacy mode is a special case */
+ if (ios->timing == MMC_TIMING_LEGACY)
+ return 0;
+
+ if (host->clock > SDHCI_DEFAULT_SDCLK_FREQ)
+ ret = xenon_hs_delay_adj(host);
+ return ret;
+}
+
+static int add_xenon_phy(struct device_node *np, struct sdhci_host *host,
+ const char *phy_name)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ int i, ret;
+
+ for (i = 0; i < NR_PHY_TYPES; i++) {
+ if (!strcmp(phy_name, phy_types[i])) {
+ priv->phy_type = i;
+ break;
+ }
+ }
+ if (i == NR_PHY_TYPES) {
+ dev_err(mmc_dev(host->mmc),
+ "Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n",
+ phy_name);
+ priv->phy_type = EMMC_5_1_PHY;
+ }
+
+ ret = alloc_emmc_phy(priv);
+ if (ret)
+ return ret;
+
+ return emmc_phy_parse_param_dt(host, np, priv->phy_params);
+}
+
+int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host)
+{
+ const char *phy_type = NULL;
+
+ if (!of_property_read_string(np, "marvell,xenon-phy-type", &phy_type))
+ return add_xenon_phy(np, host, phy_type);
+
+ dev_info(mmc_dev(host->mmc), "Fail to get Xenon PHY type. Use default eMMC 5.1 PHY\n");
+ return add_xenon_phy(np, host, "emmc 5.1 phy");
+}
diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
index 0447eba2ca51..2e47fef490be 100644
--- a/drivers/mmc/host/sdhci-xenon.c
+++ b/drivers/mmc/host/sdhci-xenon.c
@@ -244,6 +244,7 @@ static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
spin_unlock_irqrestore(&host->lock, flags);
sdhci_set_ios(mmc, ios);
+ xenon_phy_adj(host, ios);
if (host->clock > SDHCI_DEFAULT_SDCLK_FREQ) {
spin_lock_irqsave(&host->lock, flags);
@@ -482,7 +483,7 @@ static int xenon_probe_dt(struct platform_device *pdev)
}
priv->tuning_count = tuning_count;
- return err;
+ return xenon_phy_parse_dt(np, host);
}
static int xenon_sdhc_probe(struct sdhci_host *host)
diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
index d50cd663a265..86b5d2b1f1aa 100644
--- a/drivers/mmc/host/sdhci-xenon.h
+++ b/drivers/mmc/host/sdhci-xenon.h
@@ -23,8 +23,19 @@
#define SDHCI_SLOT_ENABLE_SHIFT 0
#define SDHCI_SYS_EXT_OP_CTRL 0x010C
+#define SDHCI_MASK_CMD_CONFLICT_ERROR BIT(8)
+
+#define SDHCI_SLOT_OP_STATUS_CTRL 0x0128
+
+#define SDHCI_TUN_CONSECUTIVE_TIMES_SHIFT 16
+#define SDHCI_TUN_CONSECUTIVE_TIMES_MASK 0x7
+#define SDHCI_TUN_CONSECUTIVE_TIMES 0x4
+#define SDHCI_TUNING_STEP_SHIFT 12
+#define SDHCI_TUNING_STEP_MASK 0xF
+#define SDHCI_TUNING_STEP_DIVIDER BIT(6)
#define SDHCI_SLOT_EMMC_CTRL 0x0130
+#define SDHCI_ENABLE_DATA_STROBE BIT(24)
#define SDHCI_EMMC_VCCQ_MASK 0x3
#define SDHCI_EMMC_VCCQ_1_8V 0x1
#define SDHCI_EMMC_VCCQ_3_3V 0x3
@@ -33,11 +44,17 @@
/* retuning compatible */
#define SDHCI_RETUNING_COMPATIBLE 0x1
+#define SDHCI_SLOT_EXT_PRESENT_STATE 0x014C
+#define SDHCI_DLL_LOCK_STATE 0x1
+
+#define SDHCI_SLOT_DLL_CUR_DLY_VAL 0x0150
+
/* Tuning Parameter */
#define SDHCI_TMR_RETUN_NO_PRESENT 0xF
#define SDHCI_DEF_TUNING_COUNT 0x9
#define SDHCI_DEFAULT_SDCLK_FREQ (400000)
+#define SDHCI_LOWEST_SDCLK_FREQ (100000)
/* Xenon specific Mode Select value */
#define SDHCI_XENON_CTRL_HS200 0x5
@@ -65,6 +82,28 @@ struct sdhci_xenon_priv {
* initialization completes.
*/
unsigned int init_card_type;
+
+ /*
+ * The bus_width, timing, and clock fields in below
+ * record the current ios setting of Xenon SDHC.
+ * Driver will adjust PHY setting if any change to
+ * ios affects PHY timing.
+ */
+ unsigned char bus_width;
+ unsigned char timing;
+ unsigned int clock;
+
+ int phy_type;
+ /*
+ * Contains board-specific PHY parameters
+ * passed from device tree.
+ */
+ void *phy_params;
+ struct xenon_emmc_phy_regs *emmc_phy_regs;
};
+
+int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios);
+int xenon_phy_parse_dt(struct device_node *np,
+ struct sdhci_host *host);
#endif
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 07/12] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
From: Gregory CLEMENT @ 2016-12-09 10:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
From: Hu Ziji <huziji@marvell.com>
Add Xenon eMMC/SD/SDIO host controller core functionality.
Add Xenon specific intialization process.
Add Xenon specific mmc_host_ops APIs.
Add Xenon specific register definitions.
Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
Marvell Xenon SDHC conforms to SD Physical Layer Specification
Version 3.01 and is designed according to the guidelines provided
in the SD Host Controller Standard Specification Version 3.00.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
MAINTAINERS | 1 +-
drivers/mmc/host/Kconfig | 9 +-
drivers/mmc/host/Makefile | 3 +-
drivers/mmc/host/sdhci-xenon.c | 613 ++++++++++++++++++++++++++++++++++-
drivers/mmc/host/sdhci-xenon.h | 70 ++++-
5 files changed, 696 insertions(+)
create mode 100644 drivers/mmc/host/sdhci-xenon.c
create mode 100644 drivers/mmc/host/sdhci-xenon.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 850a0afb0c8d..bb33286aeb48 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7608,6 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
M: Ziji Hu <huziji@marvell.com>
L: linux-mmc at vger.kernel.org
S: Supported
+F: drivers/mmc/host/sdhci-xenon*
F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
MATROX FRAMEBUFFER DRIVER
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 5274f503a39a..85a53623526a 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -798,3 +798,12 @@ config MMC_SDHCI_BRCMSTB
Broadcom STB SoCs.
If unsure, say Y.
+
+config MMC_SDHCI_XENON
+ tristate "Marvell Xenon eMMC/SD/SDIO SDHCI driver"
+ depends on MMC_SDHCI && MMC_SDHCI_PLTFM
+ help
+ This selects Marvell Xenon eMMC/SD/SDIO SDHCI.
+ If you have a machine with integrated Marvell Xenon SDHC IP,
+ say Y or M here.
+ If unsure, say N.
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e2bdaaf43184..75eaf743486c 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -80,3 +80,6 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o
ifeq ($(CONFIG_CB710_DEBUG),y)
CFLAGS-cb710-mmc += -DDEBUG
endif
+
+obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o
+sdhci-xenon-driver-y += sdhci-xenon.o
diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
new file mode 100644
index 000000000000..0447eba2ca51
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.c
@@ -0,0 +1,613 @@
+/*
+ * Driver for Marvell Xenon SDHC as a platform device
+ *
+ * Copyright (C) 2016 Marvell, All Rights Reserved.
+ *
+ * Author: Hu Ziji <huziji@marvell.com>
+ * Date: 2016-8-24
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * Inspired by Jisheng Zhang <jszhang@marvell.com>
+ * Special thanks to Video BG4 project team.
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include "sdhci-pltfm.h"
+#include "sdhci-xenon.h"
+
+static int enable_xenon_internal_clk(struct sdhci_host *host)
+{
+ u32 reg;
+ u8 timeout;
+
+ reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+ reg |= SDHCI_CLOCK_INT_EN;
+ sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
+ /* Wait max 20 ms */
+ timeout = 20;
+ while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
+ & SDHCI_CLOCK_INT_STABLE)) {
+ if (timeout == 0) {
+ pr_err("%s: Internal clock never stabilised.\n",
+ mmc_hostname(host->mmc));
+ return -ETIMEDOUT;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+ return 0;
+}
+
+/* Set SDCLK-off-while-idle */
+static void xenon_set_sdclk_off_idle(struct sdhci_host *host,
+ unsigned char sdhc_id, bool enable)
+{
+ u32 reg;
+ u32 mask;
+
+ reg = sdhci_readl(host, SDHCI_SYS_OP_CTRL);
+ /* Get the bit shift basing on the SDHC index */
+ mask = (0x1 << (SDHCI_SDCLK_IDLEOFF_ENABLE_SHIFT + sdhc_id));
+ if (enable)
+ reg |= mask;
+ else
+ reg &= ~mask;
+
+ sdhci_writel(host, reg, SDHCI_SYS_OP_CTRL);
+}
+
+/* Enable/Disable the Auto Clock Gating function */
+static void xenon_set_acg(struct sdhci_host *host, bool enable)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHCI_SYS_OP_CTRL);
+ if (enable)
+ reg &= ~SDHCI_AUTO_CLKGATE_DISABLE_MASK;
+ else
+ reg |= SDHCI_AUTO_CLKGATE_DISABLE_MASK;
+ sdhci_writel(host, reg, SDHCI_SYS_OP_CTRL);
+}
+
+/* Enable this SDHC */
+static void xenon_enable_sdhc(struct sdhci_host *host,
+ unsigned char sdhc_id)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHCI_SYS_OP_CTRL);
+ reg |= (BIT(sdhc_id) << SDHCI_SLOT_ENABLE_SHIFT);
+ sdhci_writel(host, reg, SDHCI_SYS_OP_CTRL);
+
+ /*
+ * Manually set the flag which all the card types require,
+ * including SD, eMMC, SDIO
+ */
+ host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
+}
+
+/* Disable this SDHC */
+static void xenon_disable_sdhc(struct sdhci_host *host,
+ unsigned char sdhc_id)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHCI_SYS_OP_CTRL);
+ reg &= ~(BIT(sdhc_id) << SDHCI_SLOT_ENABLE_SHIFT);
+ sdhci_writel(host, reg, SDHCI_SYS_OP_CTRL);
+}
+
+/* Enable Parallel Transfer Mode */
+static void xenon_enable_sdhc_parallel_tran(struct sdhci_host *host,
+ unsigned char sdhc_id)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHCI_SYS_EXT_OP_CTRL);
+ reg |= BIT(sdhc_id);
+ sdhci_writel(host, reg, SDHCI_SYS_EXT_OP_CTRL);
+}
+
+static void xenon_sdhc_tuning_setup(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ u32 reg;
+
+ /* Disable the Re-Tuning Request functionality */
+ reg = sdhci_readl(host, SDHCI_SLOT_RETUNING_REQ_CTRL);
+ reg &= ~SDHCI_RETUNING_COMPATIBLE;
+ sdhci_writel(host, reg, SDHCI_SLOT_RETUNING_REQ_CTRL);
+
+ /* Disable the Re-tuning Event Signal Enable */
+ reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
+ reg &= ~SDHCI_INT_RETUNE;
+ sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE);
+
+ /* Force to use Tuning Mode 1 */
+ host->tuning_mode = SDHCI_TUNING_MODE_1;
+ /* Set re-tuning period */
+ host->tuning_count = 1 << (priv->tuning_count - 1);
+}
+
+/*
+ * Operations inside struct sdhci_ops
+ */
+/* Recover the Register Setting cleared during SOFTWARE_RESET_ALL */
+static void sdhci_xenon_reset_exit(struct sdhci_host *host,
+ unsigned char sdhc_id, u8 mask)
+{
+ /* Only SOFTWARE RESET ALL will clear the register setting */
+ if (!(mask & SDHCI_RESET_ALL))
+ return;
+
+ /* Disable tuning request and auto-retuning again */
+ xenon_sdhc_tuning_setup(host);
+
+ xenon_set_acg(host, true);
+
+ xenon_set_sdclk_off_idle(host, sdhc_id, false);
+}
+
+static void sdhci_xenon_reset(struct sdhci_host *host, u8 mask)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ sdhci_reset(host, mask);
+ sdhci_xenon_reset_exit(host, priv->sdhc_id, mask);
+}
+
+/*
+ * Xenon defines different values for HS200 and HS400
+ * in Host_Control_2
+ */
+static void xenon_set_uhs_signaling(struct sdhci_host *host,
+ unsigned int timing)
+{
+ u16 ctrl_2;
+
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ /* Select Bus Speed Mode for host */
+ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+ if (timing == MMC_TIMING_MMC_HS200)
+ ctrl_2 |= SDHCI_XENON_CTRL_HS200;
+ else if (timing == MMC_TIMING_UHS_SDR104)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
+ else if (timing == MMC_TIMING_UHS_SDR12)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
+ else if (timing == MMC_TIMING_UHS_SDR25)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
+ else if (timing == MMC_TIMING_UHS_SDR50)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
+ else if ((timing == MMC_TIMING_UHS_DDR50) ||
+ (timing == MMC_TIMING_MMC_DDR52))
+ ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
+ else if (timing == MMC_TIMING_MMC_HS400)
+ ctrl_2 |= SDHCI_XENON_CTRL_HS400;
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+}
+
+static const struct sdhci_ops sdhci_xenon_ops = {
+ .set_clock = sdhci_set_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .reset = sdhci_xenon_reset,
+ .set_uhs_signaling = xenon_set_uhs_signaling,
+ .get_max_clock = sdhci_pltfm_clk_get_max_clock,
+};
+
+static const struct sdhci_pltfm_data sdhci_xenon_pdata = {
+ .ops = &sdhci_xenon_ops,
+ .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
+ SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
+ SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
+};
+
+/*
+ * Xenon Specific Operations in mmc_host_ops
+ */
+static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ unsigned long flags;
+ u32 reg;
+
+ /*
+ * HS400/HS200/eMMC HS doesn't have Preset Value register.
+ * However, sdhci_set_ios will read HS400/HS200 Preset register.
+ * Disable Preset Value register for HS400/HS200.
+ * eMMC HS with preset_enabled set will trigger a bug in
+ * get_preset_value().
+ */
+ spin_lock_irqsave(&host->lock, flags);
+ if ((ios->timing == MMC_TIMING_MMC_HS400) ||
+ (ios->timing == MMC_TIMING_MMC_HS200) ||
+ (ios->timing == MMC_TIMING_MMC_HS)) {
+ host->preset_enabled = false;
+ host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
+
+ reg = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ reg &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
+ sdhci_writew(host, reg, SDHCI_HOST_CONTROL2);
+ } else {
+ host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ sdhci_set_ios(mmc, ios);
+
+ if (host->clock > SDHCI_DEFAULT_SDCLK_FREQ) {
+ spin_lock_irqsave(&host->lock, flags);
+ xenon_set_sdclk_off_idle(host, priv->sdhc_id, true);
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+}
+
+static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ unsigned char voltage = ios->signal_voltage;
+ struct sdhci_host *host = mmc_priv(mmc);
+ unsigned char voltage_code;
+ u32 ctrl;
+
+ if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
+ (voltage == MMC_SIGNAL_VOLTAGE_180)) {
+ if (voltage == MMC_SIGNAL_VOLTAGE_330)
+ voltage_code = SDHCI_EMMC_VCCQ_3_3V;
+ else if (voltage == MMC_SIGNAL_VOLTAGE_180)
+ voltage_code = SDHCI_EMMC_VCCQ_1_8V;
+
+ /*
+ * This host is for eMMC, XENON self-defined
+ * eMMC control register should be accessed
+ * instead of Host Control 2
+ */
+ ctrl = sdhci_readl(host, SDHCI_SLOT_EMMC_CTRL);
+ ctrl &= ~SDHCI_EMMC_VCCQ_MASK;
+ ctrl |= voltage_code;
+ sdhci_writel(host, ctrl, SDHCI_SLOT_EMMC_CTRL);
+
+ /* There is no standard to determine this waiting period */
+ usleep_range(1000, 2000);
+
+ /* Check whether io voltage switch is done */
+ ctrl = sdhci_readl(host, SDHCI_SLOT_EMMC_CTRL);
+ ctrl &= SDHCI_EMMC_VCCQ_MASK;
+ /*
+ * This bit is set only when regulator feeds back
+ * the voltage switch results to Xenon SDHC.
+ * However, in actaul implementation, regulator might not
+ * provide this feedback.
+ * Thus we shall not rely on this bit to determine
+ * if switch failed.
+ * If the bit is not set, just throw a message.
+ * Besides, error code should not be returned.
+ */
+ if (ctrl != voltage_code)
+ dev_info(mmc_dev(mmc), "fail to detect eMMC signal voltage stable\n");
+ return 0;
+ }
+
+ dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n", voltage);
+ return -EINVAL;
+}
+
+static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ /*
+ * Before SD/SDIO set signal voltage, SD bus clock should be
+ * disabled. However, sdhci_set_clock will also disable the Internal
+ * clock in mmc_set_signal_voltage().
+ * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
+ * Thus here manually enable internal clock.
+ *
+ * After switch completes, it is unnecessary to disable internal clock,
+ * since keeping internal clock active obeys SD spec.
+ */
+ enable_xenon_internal_clk(host);
+
+ if (priv->init_card_type == MMC_TYPE_MMC)
+ return xenon_emmc_signal_voltage_switch(mmc, ios);
+
+ return sdhci_start_signal_voltage_switch(mmc, ios);
+}
+
+/*
+ * Update card type.
+ * priv->init_card_type will be used in PHY timing adjustment.
+ */
+static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ /* Update card type*/
+ priv->init_card_type = card->type;
+}
+
+static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ if (host->timing == MMC_TIMING_UHS_DDR50)
+ return 0;
+
+ return sdhci_execute_tuning(mmc, opcode);
+}
+
+static void xenon_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ u32 reg;
+ u8 sdhc_id = priv->sdhc_id;
+
+ sdhci_enable_sdio_irq(mmc, enable);
+
+ if (enable) {
+ /*
+ * Set SDIO Card Inserted indication
+ * to enable detecting SDIO async irq.
+ */
+ reg = sdhci_readl(host, SDHCI_SYS_CFG_INFO);
+ reg |= (1 << (sdhc_id + SDHCI_SLOT_TYPE_SDIO_SHIFT));
+ sdhci_writel(host, reg, SDHCI_SYS_CFG_INFO);
+ } else {
+ /* Clear SDIO Card Inserted indication */
+ reg = sdhci_readl(host, SDHCI_SYS_CFG_INFO);
+ reg &= ~(1 << (sdhc_id + SDHCI_SLOT_TYPE_SDIO_SHIFT));
+ sdhci_writel(host, reg, SDHCI_SYS_CFG_INFO);
+ }
+}
+
+static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
+{
+ host->mmc_host_ops.set_ios = xenon_set_ios;
+ host->mmc_host_ops.start_signal_voltage_switch =
+ xenon_start_signal_voltage_switch;
+ host->mmc_host_ops.init_card = xenon_init_card;
+ host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
+ host->mmc_host_ops.enable_sdio_irq = xenon_enable_sdio_irq;
+}
+
+/*
+ * Parse child node in Xenon DT.
+ * Search for the following item(s):
+ * - eMMC card type
+ */
+static int xenon_child_node_of_parse(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct mmc_host *mmc = host->mmc;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct device_node *child;
+ int nr_child;
+
+ priv->init_card_type = SDHCI_CARD_TYPE_UNKNOWN;
+
+ nr_child = of_get_child_count(np);
+ if (!nr_child)
+ return 0;
+
+ for_each_child_of_node(np, child) {
+ if (of_device_is_compatible(child, "mmc-card")) {
+ priv->init_card_type = MMC_TYPE_MMC;
+ mmc->caps |= MMC_CAP_NONREMOVABLE;
+
+ /*
+ * Force to clear BUS_TEST to
+ * skip bus_test_pre and bus_test_post
+ */
+ mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
+ mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
+ MMC_CAP2_PACKED_CMD |
+ MMC_CAP2_NO_SD |
+ MMC_CAP2_NO_SDIO;
+ }
+ of_node_put(child);
+ }
+
+ return 0;
+}
+
+static int xenon_probe_dt(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct mmc_host *mmc = host->mmc;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ int err;
+ u32 sdhc_id, nr_sdhc;
+ u32 tuning_count;
+
+ /* Standard MMC property */
+ err = mmc_of_parse(mmc);
+ if (err)
+ return err;
+
+ /* Standard SDHCI property */
+ sdhci_get_of_property(pdev);
+
+ /*
+ * Xenon Specific property:
+ * init_card_type: check whether this SDHC is for eMMC
+ * sdhc-id: the index of current SDHC.
+ * Refer to SDHCI_SYS_CFG_INFO register
+ * tun-count: the interval between re-tuning
+ */
+ /* Parse child node, including checking emmc type */
+ err = xenon_child_node_of_parse(pdev);
+ if (err)
+ return err;
+
+ priv->sdhc_id = 0x0;
+ if (!of_property_read_u32(np, "marvell,xenon-sdhc-id", &sdhc_id)) {
+ nr_sdhc = sdhci_readl(host, SDHCI_SYS_CFG_INFO);
+ nr_sdhc &= SDHCI_NR_SUPPORTED_SLOT_MASK;
+ if (unlikely(sdhc_id > nr_sdhc)) {
+ dev_err(mmc_dev(mmc), "SDHC Index %d exceeds Number of SDHCs %d\n",
+ sdhc_id, nr_sdhc);
+ return -EINVAL;
+ }
+ }
+
+ tuning_count = SDHCI_DEF_TUNING_COUNT;
+ if (!of_property_read_u32(np, "marvell,xenon-tun-count",
+ &tuning_count)) {
+ if (unlikely(tuning_count >= SDHCI_TMR_RETUN_NO_PRESENT)) {
+ dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
+ SDHCI_DEF_TUNING_COUNT);
+ tuning_count = SDHCI_DEF_TUNING_COUNT;
+ }
+ }
+ priv->tuning_count = tuning_count;
+
+ return err;
+}
+
+static int xenon_sdhc_probe(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ u8 sdhc_id = priv->sdhc_id;
+
+ /* Enable SDHC */
+ xenon_enable_sdhc(host, sdhc_id);
+
+ /* Enable ACG */
+ xenon_set_acg(host, true);
+
+ /* Enable Parallel Transfer Mode */
+ xenon_enable_sdhc_parallel_tran(host, sdhc_id);
+
+ /* Set tuning functionality of this SDHC */
+ xenon_sdhc_tuning_setup(host);
+
+ return 0;
+}
+
+static void xenon_sdhc_remove(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ u8 sdhc_id = priv->sdhc_id;
+
+ /* disable SDHC */
+ xenon_disable_sdhc(host, sdhc_id);
+}
+
+static int sdhci_xenon_probe(struct platform_device *pdev)
+{
+ struct sdhci_pltfm_host *pltfm_host;
+ struct sdhci_host *host;
+ struct sdhci_xenon_priv *priv;
+ int err;
+
+ host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
+ sizeof(struct sdhci_xenon_priv));
+ if (IS_ERR(host))
+ return PTR_ERR(host);
+
+ pltfm_host = sdhci_priv(host);
+ priv = sdhci_pltfm_priv(pltfm_host);
+
+ xenon_set_acg(host, false);
+
+ /*
+ * Link Xenon specific mmc_host_ops function,
+ * to replace standard ones in sdhci_ops.
+ */
+ xenon_replace_mmc_host_ops(host);
+
+ pltfm_host->clk = devm_clk_get(&pdev->dev, "core");
+ if (IS_ERR(pltfm_host->clk)) {
+ err = PTR_ERR(pltfm_host->clk);
+ dev_err(&pdev->dev, "Failed to setup input clk: %d\n", err);
+ goto free_pltfm;
+ }
+ err = clk_prepare_enable(pltfm_host->clk);
+ if (err)
+ goto free_pltfm;
+
+ err = xenon_probe_dt(pdev);
+ if (err)
+ goto err_clk;
+
+ err = xenon_sdhc_probe(host);
+ if (err)
+ goto err_clk;
+
+ err = sdhci_add_host(host);
+ if (err)
+ goto remove_sdhc;
+
+ return 0;
+
+remove_sdhc:
+ xenon_sdhc_remove(host);
+err_clk:
+ clk_disable_unprepare(pltfm_host->clk);
+free_pltfm:
+ sdhci_pltfm_free(pdev);
+ return err;
+}
+
+static int sdhci_xenon_remove(struct platform_device *pdev)
+{
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
+
+ xenon_sdhc_remove(host);
+
+ sdhci_remove_host(host, dead);
+
+ clk_disable_unprepare(pltfm_host->clk);
+
+ sdhci_pltfm_free(pdev);
+
+ return 0;
+}
+
+static const struct of_device_id sdhci_xenon_dt_ids[] = {
+ { .compatible = "marvell,armada-7000-sdhci",},
+ { .compatible = "marvell,armada-3700-sdhci",},
+ {}
+};
+MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
+
+static struct platform_driver sdhci_xenon_driver = {
+ .driver = {
+ .name = "xenon-sdhci",
+ .of_match_table = sdhci_xenon_dt_ids,
+ .pm = &sdhci_pltfm_pmops,
+ },
+ .probe = sdhci_xenon_probe,
+ .remove = sdhci_xenon_remove,
+};
+
+module_platform_driver(sdhci_xenon_driver);
+
+MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
+MODULE_AUTHOR("Hu Ziji <huziji@marvell.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
new file mode 100644
index 000000000000..d50cd663a265
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 Marvell, All Rights Reserved.
+ *
+ * Author: Hu Ziji <huziji@marvell.com>
+ * Date: 2016-8-24
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ */
+#ifndef SDHCI_XENON_H_
+#define SDHCI_XENON_H_
+
+
+/* Register Offset of Xenon SDHC self-defined register */
+#define SDHCI_SYS_CFG_INFO 0x0104
+#define SDHCI_SLOT_TYPE_SDIO_SHIFT 24
+#define SDHCI_NR_SUPPORTED_SLOT_MASK 0x7
+
+#define SDHCI_SYS_OP_CTRL 0x0108
+#define SDHCI_AUTO_CLKGATE_DISABLE_MASK BIT(20)
+#define SDHCI_SDCLK_IDLEOFF_ENABLE_SHIFT 8
+#define SDHCI_SLOT_ENABLE_SHIFT 0
+
+#define SDHCI_SYS_EXT_OP_CTRL 0x010C
+
+#define SDHCI_SLOT_EMMC_CTRL 0x0130
+#define SDHCI_EMMC_VCCQ_MASK 0x3
+#define SDHCI_EMMC_VCCQ_1_8V 0x1
+#define SDHCI_EMMC_VCCQ_3_3V 0x3
+
+#define SDHCI_SLOT_RETUNING_REQ_CTRL 0x0144
+/* retuning compatible */
+#define SDHCI_RETUNING_COMPATIBLE 0x1
+
+/* Tuning Parameter */
+#define SDHCI_TMR_RETUN_NO_PRESENT 0xF
+#define SDHCI_DEF_TUNING_COUNT 0x9
+
+#define SDHCI_DEFAULT_SDCLK_FREQ (400000)
+
+/* Xenon specific Mode Select value */
+#define SDHCI_XENON_CTRL_HS200 0x5
+#define SDHCI_XENON_CTRL_HS400 0x6
+
+/* Indicate Card Type is not clear yet */
+#define SDHCI_CARD_TYPE_UNKNOWN 0xF
+
+struct sdhci_xenon_priv {
+ unsigned char tuning_count;
+ /* idx of SDHC */
+ u8 sdhc_id;
+
+ /*
+ * eMMC/SD/SDIO require different PHY settings or
+ * voltage control. It's necessary for Xenon driver to
+ * recognize card type during, or even before initialization.
+ * However, mmc_host->card is not available yet at that time.
+ * This field records the card type during init.
+ * For eMMC, it is updated in dt parse. For SD/SDIO, it is
+ * updated in xenon_init_card().
+ *
+ * It is only valid during initialization after it is updated.
+ * Do not access this variable in normal transfers after
+ * initialization completes.
+ */
+ unsigned int init_card_type;
+};
+
+#endif
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 06/12] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
From: Gregory CLEMENT @ 2016-12-09 10:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
From: Hu Ziji <huziji@marvell.com>
Marvell Xenon SDHC can support eMMC/SD/SDIO.
Add Xenon-specific properties.
Also add properties for Xenon PHY setting.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt | 197 +++++++-
MAINTAINERS | 1 +-
2 files changed, 198 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
new file mode 100644
index 000000000000..c7589f8d4e3e
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
@@ -0,0 +1,197 @@
+Marvell Xenon SDHCI Controller device tree bindings
+This file documents differences between the core mmc properties
+described by mmc.txt and the properties used by the Xenon implementation.
+
+Multiple SDHCs might be put into a single Xenon IP, to save size and cost.
+Each SDHC is independent and owns independent resources, such as register sets,
+clock and PHY.
+Each SDHC should have an independent device tree node.
+
+Required Properties:
+- compatible: should be one of the following
+ - "marvell,armada-3700-sdhci": For controllers on Armada-3700 SOC.
+ Must provide a second register area and marvell,pad-type.
+ - "marvell,armada-7000-sdhci": For controllers on Armada 7K/8K SOC.
+
+- clocks:
+ Array of clocks required for SDHC.
+ Require at least input clock for Xenon IP core.
+
+- clock-names:
+ Array of names corresponding to clocks property.
+ The input clock for Xenon IP core should be named as "core".
+
+- reg:
+ * For "marvell,armada-3700-sdhci", two register areas.
+ The first one for Xenon IP register. The second one for the Armada 3700 SOC
+ PHY PAD Voltage Control register.
+ Please follow the examples with compatible "marvell,armada-3700-sdhci"
+ in below.
+ Please also check property marvell,pad-type in below.
+
+ * For other compatible strings, one register area for Xenon IP.
+
+Optional Properties:
+- mmc-card:
+ mmc-card child node must be provided when current SDHC is for eMMC.
+ Xenon SDHC often can support both SD and eMMC. This child node indicates that
+ current SDHC is for eMMC card. Thus Xenon eMMC specific configuration and
+ operations can be enabled prior to eMMC init sequence.
+ Please refer to Documentation/devicetree/bindings/mmc/mmc-card.txt.
+ This child node should not be set if current Xenon SDHC is for SD/SDIO.
+
+- bus-width:
+ When 8-bit data bus width is in use for eMMC, this property should be
+ explicitly provided and set as 8.
+ It is optional when data bus width is 4-bit or 1-bit.
+
+- mmc-ddr-1_8v:
+ Select this property when eMMC HS DDR is supported on SDHC side.
+
+- mmc-hs400-1_8v:
+ Select this property when eMMC HS400 is supported on SDHC side.
+
+- no-1-8-v:
+ Select this property when 1.8V signaling voltage supply is unavailable.
+ When this property is enabled, both mmc-ddr-1_8v and mmc-hs400-1_8v should be
+ cleared.
+
+- marvell,xenon-sdhc-id:
+ Indicate the corresponding bit index of current SDHC in
+ SDHC System Operation Control Register Bit[7:0].
+ Set/clear the corresponding bit to enable/disable current SDHC.
+ If Xenon IP contains only one SDHC, this property is optional.
+
+- marvell,xenon-phy-type:
+ Xenon support mutilple types of PHYs.
+ To select eMMC 5.1 PHY, set:
+ marvell,xenon-phy-type = "emmc 5.1 phy"
+ eMMC 5.1 PHY is the default choice if this property is not provided.
+ To select eMMC 5.0 PHY, set:
+ marvell,xenon-phy-type = "emmc 5.0 phy"
+
+ All those types of PHYs can support eMMC, SD and SDIO.
+ Please note that this property only presents the type of PHY.
+ It doesn't stand for the entire SDHC type or property.
+ For example, "emmc 5.1 phy" doesn't mean that this Xenon SDHC only supports
+ eMMC 5.1.
+
+- marvell,xenon-phy-znr:
+ Set PHY ZNR value.
+ Only available for eMMC PHY 5.1 and eMMC PHY 5.0.
+ Valid range = [0:0x1F].
+ ZNR is set as 0xF by default if this property is not provided.
+
+- marvell,xenon-phy-zpr:
+ Set PHY ZPR value.
+ Only available for eMMC PHY 5.1 and eMMC PHY 5.0.
+ Valid range = [0:0x1F].
+ ZPR is set as 0xF by default if this property is not provided.
+
+- marvell,xenon-phy-nr-success-tun:
+ Set the number of required consecutive successful sampling points used to
+ identify a valid sampling window, in tuning process.
+ Valid range = [1:7].
+ Set as 0x4 by default if this property is not provided.
+
+- marvell,xenon-phy-tun-step-divider:
+ Set the divider for calculating TUN_STEP.
+ Set as 64 by default if this property is not provided.
+
+- marvell,xenon-phy-slow-mode:
+ If this property is selected, transfers will bypass PHY.
+ Only available when bus frequency lower than 55MHz in SDR mde.
+ Disabled by default. Please only try this property if timing issues always
+ occur with PHY enabled in eMMC HS SDR, SD SDR12, SD SDR25, SD SDR50 mode.
+
+- marvell,xenon-tun-count:
+ Xenon SDHC SOC usually doesn't provide re-tuning counter in
+ Capabilities Register 3 Bit[11:8].
+ This property provides the re-tuning counter.
+ If this property is not set, default re-tuning counter will
+ be set as 0x9 in driver.
+
+- marvell,pad-type:
+ Type of Armada 3700 SOC PHY PAD Voltage Controller register.
+ Only valid when "marvell,armada-3700-sdhci" is selected.
+ Two types: "sd" and "fixed-1-8v".
+ If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
+ switched to 1.8V when SD in UHS-I.
+ If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.
+ Please follow the examples with compatible "marvell,armada-3700-sdhci"
+ in below.
+
+Example:
+- For eMMC:
+
+ sdhci at aa0000 {
+ compatible = "marvell,armada-7000-sdhci";
+ reg = <0xaa0000 0x1000>;
+ interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+ clocks = <&emmc_clk>;
+ clock-names = "core";
+ bus-width = <8>;
+ mmc-ddr-1_8v;
+ mmc-hs400-1_8v;
+ marvell,xenon-sdhc-id = <0>;
+ marvell,xenon-phy-type = "emmc 5.1 phy";
+ marvell,xenon-tun-count = <11>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+ mmccard: mmccard at 0 {
+ compatible = "mmc-card";
+ reg = <0>;
+ };
+ };
+
+- For SD/SDIO:
+
+ sdhci at ab0000 {
+ compatible = "marvell,armada-7000-sdhci";
+ reg = <0xab0000 0x1000>;
+ interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+ vqmmc-supply = <&sd_regulator>;
+ clocks = <&sdclk>;
+ clock-names = "core";
+ bus-width = <4>;
+ marvell,xenon-tun-count = <9>;
+ };
+
+- For eMMC with compatible "marvell,armada-3700-sdhci":
+
+ sdhci at aa0000 {
+ compatible = "marvell,armada-3700-sdhci";
+ reg = <0xaa0000 0x1000>,
+ <phy_addr 0x4>;
+ interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+ clocks = <&emmcclk>;
+ clock-names = "core";
+ bus-width = <8>;
+ mmc-ddr-1_8v;
+ mmc-hs400-1_8v;
+
+ marvell,pad-type = "fixed-1-8v";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+ mmccard: mmccard at 0 {
+ compatible = "mmc-card";
+ reg = <0>;
+ };
+ };
+
+- For SD/SDIO with compatible "marvell,armada-3700-sdhci":
+
+ sdhci at ab0000 {
+ compatible = "marvell,armada-3700-sdhci";
+ reg = <0xab0000 0x1000>,
+ <phy_addr 0x4>;
+ interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+ vqmmc-supply = <&sd_regulator>;
+ clocks = <&sdclk>;
+ clock-names = "core";
+ bus-width = <4>;
+
+ marvell,pad-type = "sd";
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 1a5c4c30ea24..850a0afb0c8d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7608,6 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
M: Ziji Hu <huziji@marvell.com>
L: linux-mmc at vger.kernel.org
S: Supported
+F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
MATROX FRAMEBUFFER DRIVER
L: linux-fbdev at vger.kernel.org
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 05/12] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
From: Gregory CLEMENT @ 2016-12-09 10:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
From: Hu Ziji <huziji@marvell.com>
Add maintainer entry for Marvell Xenon eMMC/SD/SDIO Host
Controller drivers.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
MAINTAINERS | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c44795306342..1a5c4c30ea24 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7604,6 +7604,11 @@ M: Nicolas Pitre <nico@fluxnic.net>
S: Odd Fixes
F: drivers/mmc/host/mvsdio.*
+MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
+M: Ziji Hu <huziji@marvell.com>
+L: linux-mmc at vger.kernel.org
+S: Supported
+
MATROX FRAMEBUFFER DRIVER
L: linux-fbdev at vger.kernel.org
S: Orphan
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 04/12] mmc: sdhci: Export sdhci_enable_sdio_irq() from sdhci.c
From: Gregory CLEMENT @ 2016-12-09 10:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
From: Hu Ziji <huziji@marvell.com>
Export sdhci_enable_sdio_irq() from sdhci.c. Thus vendor SDHC
driver can implement its specific SDIO irq contorl.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
drivers/mmc/host/sdhci.c | 3 ++-
drivers/mmc/host/sdhci.h | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index e971abb1368f..e174379ee019 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1817,7 +1817,7 @@ static void sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable)
}
}
-static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
{
struct sdhci_host *host = mmc_priv(mmc);
unsigned long flags;
@@ -1831,6 +1831,7 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
sdhci_enable_sdio_irq_nolock(host, enable);
spin_unlock_irqrestore(&host->lock, flags);
}
+EXPORT_SYMBOL_GPL(sdhci_enable_sdio_irq);
int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
struct mmc_ios *ios)
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 95beadc66849..a3e8913452fc 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -692,6 +692,7 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
struct mmc_ios *ios);
int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
+void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable);
#ifdef CONFIG_PM
extern int sdhci_suspend_host(struct sdhci_host *host);
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 03/12] mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
From: Gregory CLEMENT @ 2016-12-09 10:29 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
From: Hu Ziji <huziji@marvell.com>
Export sdhci_execute_tuning() from sdhci.c.
Thus vendor sdhci driver can execute its own tuning process.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
drivers/mmc/host/sdhci.c | 3 ++-
drivers/mmc/host/sdhci.h | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 8e6e4e37e3b4..e971abb1368f 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1950,7 +1950,7 @@ static int sdhci_prepare_hs400_tuning(struct mmc_host *mmc, struct mmc_ios *ios)
return 0;
}
-static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
+int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
{
struct sdhci_host *host = mmc_priv(mmc);
u16 ctrl;
@@ -2139,6 +2139,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
spin_unlock_irqrestore(&host->lock, flags);
return err;
}
+EXPORT_SYMBOL_GPL(sdhci_execute_tuning);
static int sdhci_select_drive_strength(struct mmc_card *card,
unsigned int max_dtr, int host_drv,
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index cd18b6f19c3b..95beadc66849 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -691,6 +691,7 @@ void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
struct mmc_ios *ios);
+int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
#ifdef CONFIG_PM
extern int sdhci_suspend_host(struct sdhci_host *host);
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 02/12] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
From: Gregory CLEMENT @ 2016-12-09 10:29 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
From: Hu Ziji <huziji@marvell.com>
Export sdhci_start_signal_voltage_switch() from sdhci.c.
Thus vendor sdhci driver can implement its own signal voltage
switch routine.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
drivers/mmc/host/sdhci.c | 5 +++--
drivers/mmc/host/sdhci.h | 2 ++
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index ea06faf8a437..8e6e4e37e3b4 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1832,8 +1832,8 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
spin_unlock_irqrestore(&host->lock, flags);
}
-static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
- struct mmc_ios *ios)
+int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
{
struct sdhci_host *host = mmc_priv(mmc);
u16 ctrl;
@@ -1925,6 +1925,7 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
return 0;
}
}
+EXPORT_SYMBOL_GPL(sdhci_start_signal_voltage_switch);
static int sdhci_card_busy(struct mmc_host *mmc)
{
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 37771de4cafa..cd18b6f19c3b 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -689,6 +689,8 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
void sdhci_reset(struct sdhci_host *host, u8 mask);
void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
+int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios);
#ifdef CONFIG_PM
extern int sdhci_suspend_host(struct sdhci_host *host);
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 01/12] mmc: sdhci: Export sdhci_set_ios() from sdhci.c
From: Gregory CLEMENT @ 2016-12-09 10:29 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.e0137cef24a16a2c7d795548de554993595d4a98.1481279228.git-series.gregory.clement@free-electrons.com>
From: Hu Ziji <huziji@marvell.com>
Export sdhci_set_ios() in sdhci.c.
Thus vendor sdhci driver can implement its own set_ios() routine.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
drivers/mmc/host/sdhci.c | 3 ++-
drivers/mmc/host/sdhci.h | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 71654b90227f..ea06faf8a437 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1563,7 +1563,7 @@ void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing)
}
EXPORT_SYMBOL_GPL(sdhci_set_uhs_signaling);
-static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct sdhci_host *host = mmc_priv(mmc);
unsigned long flags;
@@ -1723,6 +1723,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
}
+EXPORT_SYMBOL_GPL(sdhci_set_ios);
static int sdhci_get_cd(struct mmc_host *mmc)
{
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 766df17fb7eb..37771de4cafa 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -688,6 +688,7 @@ void sdhci_set_power_noreg(struct sdhci_host *host, unsigned char mode,
void sdhci_set_bus_width(struct sdhci_host *host, int width);
void sdhci_reset(struct sdhci_host *host, u8 mask);
void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
+void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
#ifdef CONFIG_PM
extern int sdhci_suspend_host(struct sdhci_host *host);
--
git-series 0.9.1
^ permalink raw reply related
* [PATCH v3 00/12] mmc: Add support to Marvell Xenon SD Host Controller
From: Gregory CLEMENT @ 2016-12-09 10:29 UTC (permalink / raw)
To: linux-arm-kernel
Hello,
This the third version of the series adding support for the SDHCI
Xenon controller. It can be currently found on the Armada 37xx and the
Armada 7K/8K but will be also used in more Marvell SoC (and not only
the mvebu ones actually).
I think that now most (if not all) the remarks had been taking into
account since the second version. According to Ziji Hu, here are the
following changes:
" Changes in V3:
Adjust and improve Xenon DT bindings. Move some caps setting from driver into
DT. Use mmc-card sub-node to represent eMMC type.
Remove PHY Sampling Fixed Delay Line scan in lower speed mode.
Improve Xenon probe and ->init_card() functions.
Export sdhci_enable_sdio_irq() and implement own SDIO IRQ control.
Split PHY patch into two smaller patches.
Temporarily remove AXI clock before its implementation is improved."
Besides this changes I also
- Removed the sdhci-xenon-phy.h and moved its content in the
shc-xenon-phy.c file.
- Fixed the tuning-count usage
- Managed the error case for clk_prepare_enable
For the record the change from v1 was:
" Changes in V2:
rebase on v4.9-rc2.
Re-write Xenon bindings. Ajust Xenon DT property naming.
Add a new DT property to indicate eMMC card type, instead of using
variable card_candidate.
Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
Add support to HS400 retuning."
Thanks,
Gregory
Gregory CLEMENT (3):
arm64: dts: marvell: add eMMC support for Armada 37xx
arm64: dts: marvell: add sdhci support for Armada 7K/8K
arm64: configs: enable SDHCI driver for Xenon
Hu Ziji (9):
mmc: sdhci: Export sdhci_set_ios() from sdhci.c
mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
mmc: sdhci: Export sdhci_enable_sdio_irq() from sdhci.c
MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
dt: bindings: Add bindings for Marvell Xenon SD Host Controller
mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC.
mmc: sdhci-xenon: Add SOC PHY PAD voltage control
Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt | 197 ++-
MAINTAINERS | 7 +-
arch/arm64/boot/dts/marvell/armada-3720-db.dts | 17 +-
arch/arm64/boot/dts/marvell/armada-37xx.dtsi | 11 +-
arch/arm64/boot/dts/marvell/armada-7040-db.dts | 7 +-
arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 9 +-
arch/arm64/configs/defconfig | 1 +-
drivers/mmc/host/Kconfig | 9 +-
drivers/mmc/host/Makefile | 3 +-
drivers/mmc/host/sdhci-xenon-phy.c | 908 +++++++-
drivers/mmc/host/sdhci-xenon.c | 616 +++++-
drivers/mmc/host/sdhci-xenon.h | 111 +-
drivers/mmc/host/sdhci.c | 14 +-
drivers/mmc/host/sdhci.h | 5 +-
14 files changed, 1910 insertions(+), 5 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
create mode 100644 drivers/mmc/host/sdhci-xenon.c
create mode 100644 drivers/mmc/host/sdhci-xenon.h
base-commit: 9fe68cad6e74967b88d0c6aeca7d9cd6b6e91942
--
git-series 0.9.1
^ permalink raw reply
* Tearing down DMA transfer setup after DMA client has finished
From: Sebastian Frias @ 2016-12-09 10:25 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161209065955.GJ6408@localhost>
On 09/12/16 07:59, Vinod Koul wrote:
> On Thu, Dec 08, 2016 at 04:48:18PM +0000, M?ns Rullg?rd wrote:
>> Vinod Koul <vinod.koul@intel.com> writes:
>>
>>> To make it efficient, disregarding your Sbox HW issue, the solution is
>>> virtual channels. You can delink physical channels and virtual channels. If
>>> one has SW controlled MUX then a channel can service any client. For few
>>> controllers request lines are hard wired so they cant use any channel. But
>>> if you dont have this restriction then driver can queue up many transactions
>>> from different controllers.
>>
>> Have you been paying attention at all? This exactly what the driver
>> ALREADY DOES.
>
> And have you read what the question was?
>
I think many people appreciate the quick turn around time and responsiveness of
knowledgeable people making constructive remarks in this thread, but it looks we
are slowly drifting away from the main problem.
If we had to sum up the discussion, the current DMA API/framework in Linux seems
to lack a way to properly handle this HW (or if it has a way, the information got
lost somewhere).
What concrete solution do you propose?
Alternatively, one can think of the current issue (i.e.: the fact that the IRQ
arrives "too soon") in a different way.
Instead of thinking the IRQ indicates "transfer complete", it is indicating "ready
to accept another command", which in practice (and with proper API support) can
translate into efficient queuing of DMA operations.
^ permalink raw reply
* [RFC, PATCHv1 00/28] 5-level paging
From: Arnd Bergmann @ 2016-12-09 10:24 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161209050130.GC2595@gmail.com>
On Friday, December 9, 2016 6:01:30 AM CET Ingo Molnar wrote:
> > - Handle opt-in wider address space for userspace.
> >
> > Not all userspace is ready to handle addresses wider than current
> > 47-bits. At least some JIT compiler make use of upper bits to encode
> > their info.
> >
> > We need to have an interface to opt-in wider addresses from userspace
> > to avoid regressions.
> >
> > For now, I've included testing-only patch which bumps TASK_SIZE to
> > 56-bits. This can be handy for testing to see what breaks if we max-out
> > size of virtual address space.
>
> So this is just a detail - but it sounds a bit limiting to me to provide an 'opt
> in' flag for something that will work just fine on the vast majority of 64-bit
> software.
>
> Please make this an opt out compatibility flag instead: similar to how we handle
> address space layout limitations/quirks ABI details, such as ADDR_LIMIT_32BIT,
> ADDR_LIMIT_3GB, ADDR_COMPAT_LAYOUT, READ_IMPLIES_EXEC, etc.
We've had a similar discussion about JIT software on ARM64, which has a wide
range of supported page table layouts and some software wants to limit that
to a specific number.
I don't remember the outcome of that discussion, but I'm adding a few people
to Cc that might remember.
There have also been some discussions in the past to make the depth of the
page table a per-task decision on s390, since you may have some tasks that
run just fine with two or three levels of paging while another task actually
wants the full 64-bit address space. I wonder how much extra work this would
be on top of the boot-time option.
Arnd
^ permalink raw reply
* [PATCH v8 3/3] iio: adc: add support for Allwinner SoCs ADC
From: Quentin Schulz @ 2016-12-09 10:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161209102236.17655-1-quentin.schulz@free-electrons.com>
The Allwinner SoCs all have an ADC that can also act as a touchscreen
controller and a thermal sensor. This patch adds the ADC driver which is
based on the MFD for the same SoCs ADC.
This also registers the thermal adc channel in the iio map array so
iio_hwmon could use it without modifying the Device Tree. This registers
the driver in the thermal framework.
The thermal sensor requires the IP to be in touchscreen mode to return
correct values. Therefore, if the user is continuously reading the ADC
channel(s), the thermal framework in which the thermal sensor is
registered will switch the IP in touchscreen mode to get a temperature
value and requires a delay of 100ms (because of the mode switching),
then the ADC will switch back to ADC mode and requires also a delay of
100ms. If the ADC readings are critical to user and the SoC temperature
is not, this driver is capable of not registering the thermal sensor in
the thermal framework and thus, "quicken" the ADC readings.
This driver probes on three different platform_device_id to take into
account slight differences (registers bit and temperature computation)
between Allwinner SoCs ADCs.
Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Acked-by: Jonathan Cameron <jic23@kernel.org>
Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
---
v8:
- remove Kconfig depends on !TOUCHSCREEN_SUN4I (moved to
MFD_SUN4I_GPADC),
- fix return values of regmap_irq_get_virq and platform_get_irq_byname
stored in an unsigned int and then check if negative,
- fix uninitialized ret value when an error occurs while registering
the thermal sensor in the framework,
v7:
- add Kconfig depends on !TOUCHSCREEN_SUN4I,
- remove Kconfig selects THERMAL_OF,
- do not register thermal sensor if CONFIG_THERMAL_OF is disabled,
- disable irq in irq_handler rather than in read_raw,
- add delay when switching the IP's mode or channel (delay empirically found),
- quicken thermal sensor interrupt period,
- add masks for channel bits,
- fix deadlock in sun4i_gpadc_read if regmap_read/write fails,
- move some logic from sun4i_gpadc_read to sun4i_prepare_for_irq,
- mark last busy for runtime_pm only on success in sun4i_gpadc_read,
- remove cached values,
- increase wait_for_completion_timeout timeout to 1s to be sure to not miss the
thermal interrupt,
- add voltage scale,
- use devm_iio_device_register,
v6:
- remove "-mfd" from filenames and variables inside MFD driver,
- use DEFINE_RES_IRQ_NAMED instead of setting resources manually,
- cosmetic changes,
- use IDs and switch over ID to get cells specific to an architecture, instead
of using cells direclty, in of_device_id.data,
- compute size of mfd_cells array instead of hardcoded one,
v5:
- correct mail address,
v4:
- rename files and variables from sunxi* to sun4i*,
- rename defines from SUNXI_* to SUN4I_* or SUN6I_*,
- remove TP in defines name,
- rename SUNXI_IRQ_* to SUN4I_GPADC_IRQ_* for consistency,
- use devm functions for regmap_add_irq_chip and mfd_add_devices,
- remove remove functions (now empty thanks to devm functions),
v3:
- use defines in regmap_irq instead of hard coded BITs,
- use of_device_id data field to chose which MFD cells to add considering
the compatible responsible of the MFD probe,
- remove useless initializations,
- disable all interrupts before adding them to regmap_irqchip,
- add goto error label in probe,
- correct wrapping in header license,
- move defines from IIO driver to header,
- use GENMASK to limit the size of the variable passed to a macro,
- prefix register BIT defines with the name of the register,
- reorder defines,
v2:
- add license headers,
- reorder alphabetically includes,
- add SUNXI_GPADC_ prefixes for defines,
drivers/iio/adc/Kconfig | 16 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/sun4i-gpadc-iio.c | 612 ++++++++++++++++++++++++++++++++++++++
include/linux/mfd/sun4i-gpadc.h | 2 +
4 files changed, 631 insertions(+)
create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 99c0514..67ed278 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -434,6 +434,22 @@ config STX104
The base port addresses for the devices may be configured via the base
array module parameter.
+config SUN4I_GPADC
+ tristate "Support for the Allwinner SoCs GPADC"
+ depends on IIO
+ depends on MFD_SUN4I_GPADC
+ help
+ Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
+ GPADC. This ADC provides 4 channels which can be used as an ADC or as
+ a touchscreen input and one channel for thermal sensor.
+
+ The thermal sensor is activated by default but slows down ADC
+ readings. You can disable CONFIG_THERMAL_OF to disable the CPU thermal
+ sensor if you want faster ADC readings.
+
+ To compile this driver as a module, choose M here: the module will be
+ called sun4i-gpadc-iio.
+
config TI_ADC081C
tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 7a40c04..18ce8d6 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
obj-$(CONFIG_STX104) += stx104.o
+obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
new file mode 100644
index 0000000..6421541
--- /dev/null
+++ b/drivers/iio/adc/sun4i-gpadc-iio.c
@@ -0,0 +1,612 @@
+/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
+ *
+ * Copyright (c) 2016 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.
+ *
+ * The Allwinner SoCs all have an ADC that can also act as a touchscreen
+ * controller and a thermal sensor.
+ * The thermal sensor works only when the ADC acts as a touchscreen controller
+ * and is configured to throw an interrupt every fixed periods of time (let say
+ * every X seconds).
+ * One would be tempted to disable the IP on the hardware side rather than
+ * disabling interrupts to save some power but that resets the internal clock of
+ * the IP, resulting in having to wait X seconds every time we want to read the
+ * value of the thermal sensor.
+ * This is also the reason of using autosuspend in pm_runtime. If there was no
+ * autosuspend, the thermal sensor would need X seconds after every
+ * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
+ * thermal sensor to be requested again in a certain time span before it gets
+ * shutdown for not being used.
+ */
+
+#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/delay.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/driver.h>
+#include <linux/iio/machine.h>
+#include <linux/mfd/sun4i-gpadc.h>
+
+static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
+{
+ return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
+}
+
+static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
+{
+ return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
+}
+
+struct gpadc_data {
+ int temp_offset;
+ int temp_scale;
+ unsigned int tp_mode_en;
+ unsigned int tp_adc_select;
+ unsigned int (*adc_chan_select)(unsigned int chan);
+ unsigned int adc_chan_mask;
+};
+
+static const struct gpadc_data sun4i_gpadc_data = {
+ .temp_offset = -1932,
+ .temp_scale = 133,
+ .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
+ .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
+ .adc_chan_select = &sun4i_gpadc_chan_select,
+ .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
+};
+
+static const struct gpadc_data sun5i_gpadc_data = {
+ .temp_offset = -1447,
+ .temp_scale = 100,
+ .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
+ .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
+ .adc_chan_select = &sun4i_gpadc_chan_select,
+ .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
+};
+
+static const struct gpadc_data sun6i_gpadc_data = {
+ .temp_offset = -1623,
+ .temp_scale = 167,
+ .tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
+ .tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
+ .adc_chan_select = &sun6i_gpadc_chan_select,
+ .adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK,
+};
+
+struct sun4i_gpadc_iio {
+ struct iio_dev *indio_dev;
+ struct completion completion;
+ int temp_data;
+ u32 adc_data;
+ struct regmap *regmap;
+ unsigned int fifo_data_irq;
+ atomic_t ignore_fifo_data_irq;
+ unsigned int temp_data_irq;
+ atomic_t ignore_temp_data_irq;
+ const struct gpadc_data *data;
+ /* prevents concurrent reads of temperature and ADC */
+ struct mutex mutex;
+};
+
+#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = _channel, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .datasheet_name = _name, \
+}
+
+static struct iio_map sun4i_gpadc_hwmon_maps[] = {
+ {
+ .adc_channel_label = "temp_adc",
+ .consumer_dev_name = "iio_hwmon.0",
+ },
+ { /* sentinel */ },
+};
+
+static const struct iio_chan_spec sun4i_gpadc_channels[] = {
+ SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
+ SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
+ SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
+ SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ .datasheet_name = "temp_adc",
+ },
+};
+
+static const struct iio_chan_spec sun4i_gpadc_channels_no_temp[] = {
+ SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
+ SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
+ SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
+ SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
+};
+
+static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel,
+ unsigned int irq)
+{
+ struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+ int ret;
+ u32 reg;
+
+ pm_runtime_get_sync(indio_dev->dev.parent);
+
+ reinit_completion(&info->completion);
+
+ ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
+ SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
+ SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(info->regmap, SUN4I_GPADC_CTRL1, ®);
+ if (ret)
+ return ret;
+
+ if (irq == info->fifo_data_irq) {
+ ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
+ info->data->tp_mode_en |
+ info->data->tp_adc_select |
+ info->data->adc_chan_select(channel));
+ /*
+ * When the IP changes channel, it needs a bit of time to get
+ * correct values.
+ */
+ if ((reg & info->data->adc_chan_mask) !=
+ info->data->adc_chan_select(channel))
+ mdelay(10);
+
+ } else {
+ /*
+ * The temperature sensor returns valid data only when the ADC
+ * operates in touchscreen mode.
+ */
+ ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
+ info->data->tp_mode_en);
+ }
+
+ if (ret)
+ return ret;
+
+ /*
+ * When the IP changes mode between ADC or touchscreen, it
+ * needs a bit of time to get correct values.
+ */
+ if ((reg & info->data->tp_adc_select) != info->data->tp_adc_select)
+ mdelay(100);
+
+ return 0;
+}
+
+static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
+ unsigned int irq)
+{
+ struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&info->mutex);
+
+ ret = sun4i_prepare_for_irq(indio_dev, channel, irq);
+ if (ret)
+ goto err;
+
+ enable_irq(irq);
+
+ /*
+ * The temperature sensor throws an interruption periodically (currently
+ * set at periods of ~0.6s in sun4i_gpadc_runtime_resume). A 1s delay
+ * makes sure an interruption occurs in normal conditions. If it doesn't
+ * occur, then there is a timeout.
+ */
+ if (!wait_for_completion_timeout(&info->completion,
+ msecs_to_jiffies(1000))) {
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+
+ if (irq == info->fifo_data_irq)
+ *val = info->adc_data;
+ else
+ *val = info->temp_data;
+
+ ret = 0;
+ pm_runtime_mark_last_busy(indio_dev->dev.parent);
+
+err:
+ pm_runtime_put_autosuspend(indio_dev->dev.parent);
+ mutex_unlock(&info->mutex);
+
+ return ret;
+}
+
+static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
+ int *val)
+{
+ struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+ return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
+}
+
+static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
+{
+ struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+ return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
+}
+
+static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
+{
+ struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+ *val = info->data->temp_offset;
+
+ return 0;
+}
+
+static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
+{
+ struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+ *val = info->data->temp_scale;
+
+ return 0;
+}
+
+static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_OFFSET:
+ ret = sun4i_gpadc_temp_offset(indio_dev, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_RAW:
+ if (chan->type == IIO_VOLTAGE)
+ ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
+ val);
+ else
+ ret = sun4i_gpadc_temp_read(indio_dev, val);
+
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_VOLTAGE) {
+ /* 3000mV / 4096 * raw */
+ *val = 0;
+ *val2 = 732421875;
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ ret = sun4i_gpadc_temp_scale(indio_dev, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info sun4i_gpadc_iio_info = {
+ .read_raw = sun4i_gpadc_read_raw,
+ .driver_module = THIS_MODULE,
+};
+
+static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
+{
+ struct sun4i_gpadc_iio *info = dev_id;
+
+ if (atomic_read(&info->ignore_temp_data_irq))
+ goto out;
+
+ if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
+ complete(&info->completion);
+
+out:
+ disable_irq_nosync(info->temp_data_irq);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
+{
+ struct sun4i_gpadc_iio *info = dev_id;
+
+ if (atomic_read(&info->ignore_fifo_data_irq))
+ goto out;
+
+ if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
+ complete(&info->completion);
+
+out:
+ disable_irq_nosync(info->fifo_data_irq);
+ return IRQ_HANDLED;
+}
+
+static int sun4i_gpadc_runtime_suspend(struct device *dev)
+{
+ struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
+
+ /* Disable the ADC on IP */
+ regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
+ /* Disable temperature sensor on IP */
+ regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
+
+ return 0;
+}
+
+static int sun4i_gpadc_runtime_resume(struct device *dev)
+{
+ struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
+
+ /* clkin = 6MHz */
+ regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
+ SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
+ SUN4I_GPADC_CTRL0_FS_DIV(7) |
+ SUN4I_GPADC_CTRL0_T_ACQ(63));
+ regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
+ regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
+ SUN4I_GPADC_CTRL3_FILTER_EN |
+ SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
+ /* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~0.6s */
+ regmap_write(info->regmap, SUN4I_GPADC_TPR,
+ SUN4I_GPADC_TPR_TEMP_ENABLE |
+ SUN4I_GPADC_TPR_TEMP_PERIOD(800));
+
+ return 0;
+}
+
+static int sun4i_gpadc_get_temp(void *data, int *temp)
+{
+ struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
+ int val, scale, offset;
+
+ if (sun4i_gpadc_temp_read(info->indio_dev, &val))
+ return -ETIMEDOUT;
+
+ sun4i_gpadc_temp_scale(info->indio_dev, &scale);
+ sun4i_gpadc_temp_offset(info->indio_dev, &offset);
+
+ *temp = (val + offset) * scale;
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
+ .get_temp = &sun4i_gpadc_get_temp,
+};
+
+static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
+ .runtime_suspend = &sun4i_gpadc_runtime_suspend,
+ .runtime_resume = &sun4i_gpadc_runtime_resume,
+};
+
+static int sun4i_irq_init(struct platform_device *pdev, const char *name,
+ irq_handler_t handler, const char *devname,
+ unsigned int *irq, atomic_t *atomic)
+{
+ int ret;
+ struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
+ struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
+
+ /*
+ * Once the interrupt is activated, the IP continuously performs
+ * conversions thus throws interrupts. The interrupt is activated right
+ * after being requested but we want to control when these interrupts
+ * occur thus we disable it right after being requested. However, an
+ * interrupt might occur between these two instructions and we have to
+ * make sure that does not happen, by using atomic flags. We set the
+ * flag before requesting the interrupt and unset it right after
+ * disabling the interrupt. When an interrupt occurs between these two
+ * instructions, reading the atomic flag will tell us to ignore the
+ * interrupt.
+ */
+ atomic_set(atomic, 1);
+
+ ret = platform_get_irq_byname(pdev, name);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "no %s interrupt registered\n", name);
+ return ret;
+ }
+
+ ret = regmap_irq_get_virq(mfd_dev->regmap_irqc, ret);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to get virq for irq %s\n", name);
+ return ret;
+ }
+
+ *irq = ret;
+ ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
+ devname, info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
+ name, ret);
+ return ret;
+ }
+
+ disable_irq(*irq);
+ atomic_set(atomic, 0);
+
+ return 0;
+}
+
+static int sun4i_gpadc_probe(struct platform_device *pdev)
+{
+ struct sun4i_gpadc_iio *info;
+ struct iio_dev *indio_dev;
+ int ret;
+ struct sun4i_gpadc_dev *sun4i_gpadc_dev;
+ struct thermal_zone_device *tzd;
+
+ sun4i_gpadc_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);
+
+ mutex_init(&info->mutex);
+ info->regmap = sun4i_gpadc_dev->regmap;
+ info->indio_dev = indio_dev;
+ init_completion(&info->completion);
+ indio_dev->name = dev_name(&pdev->dev);
+ indio_dev->dev.parent = &pdev->dev;
+ indio_dev->dev.of_node = pdev->dev.of_node;
+ indio_dev->info = &sun4i_gpadc_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
+ indio_dev->channels = sun4i_gpadc_channels;
+
+ info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
+
+ /*
+ * Since the thermal sensor needs the IP to be in touchscreen mode and
+ * there is no register to know if the IP has finished its transition
+ * between the two modes, a delay is required when switching modes. This
+ * slows down ADC readings while the latter are critical data to the
+ * user. Disabling CONFIG_THERMAL_OF in kernel configuration allows the
+ * user to avoid registering the thermal sensor (thus unavailable) and
+ * does not switch between modes thus "quicken" the ADC readings.
+ * The thermal sensor should be enabled by default since the SoC
+ * temperature is usually more critical than ADC readings.
+ */
+
+ if (IS_ENABLED(CONFIG_THERMAL_OF)) {
+ /*
+ * This driver is a child of an MFD which has a node in the DT but not
+ * its children. Therefore, the resulting devices of this driver do not
+ * have an of_node variable.
+ * However, its parent (the MFD driver) has an of_node variable and
+ * since devm_thermal_zone_of_sensor_register uses its first argument to
+ * match the phandle defined in the node of the thermal driver with the
+ * of_node of the device passed as first argument and the third argument
+ * to call ops from thermal_zone_of_device_ops, the solution is to use
+ * the parent device as first argument to match the phandle with its
+ * of_node, and the device from this driver as third argument to return
+ * the temperature.
+ */
+ tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0,
+ info,
+ &sun4i_ts_tz_ops);
+ if (IS_ERR(tzd)) {
+ dev_err(&pdev->dev,
+ "could not register thermal sensor: %ld\n",
+ PTR_ERR(tzd));
+ ret = PTR_ERR(tzd);
+ goto err;
+ }
+ } else {
+ indio_dev->num_channels =
+ ARRAY_SIZE(sun4i_gpadc_channels_no_temp);
+ indio_dev->channels = sun4i_gpadc_channels_no_temp;
+ }
+
+ pm_runtime_set_autosuspend_delay(&pdev->dev,
+ SUN4I_GPADC_AUTOSUSPEND_DELAY);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ if (IS_ENABLED(CONFIG_THERMAL_OF)) {
+ ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
+ sun4i_gpadc_temp_data_irq_handler,
+ "temp_data", &info->temp_data_irq,
+ &info->ignore_temp_data_irq);
+ if (ret < 0)
+ goto err;
+ }
+
+ ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
+ sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
+ &info->fifo_data_irq, &info->ignore_fifo_data_irq);
+ if (ret < 0)
+ goto err;
+
+ if (IS_ENABLED(CONFIG_THERMAL_OF)) {
+ ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "failed to register iio map array\n");
+ goto err;
+ }
+ }
+
+ ret = devm_iio_device_register(&pdev->dev, indio_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not register the device\n");
+ goto err_map;
+ }
+
+ return 0;
+
+err_map:
+ if (IS_ENABLED(CONFIG_THERMAL_OF))
+ iio_map_array_unregister(indio_dev);
+
+err:
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static int sun4i_gpadc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ if (IS_ENABLED(CONFIG_THERMAL_OF))
+ iio_map_array_unregister(indio_dev);
+
+ return 0;
+}
+
+static const struct platform_device_id sun4i_gpadc_id[] = {
+ { "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
+ { "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
+ { "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
+ { /* sentinel */ },
+};
+
+static struct platform_driver sun4i_gpadc_driver = {
+ .driver = {
+ .name = "sun4i-gpadc-iio",
+ .pm = &sun4i_gpadc_pm_ops,
+ },
+ .id_table = sun4i_gpadc_id,
+ .probe = sun4i_gpadc_probe,
+ .remove = sun4i_gpadc_remove,
+};
+
+module_platform_driver(sun4i_gpadc_driver);
+
+MODULE_DESCRIPTION("ADC driver for sunxi platforms");
+MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/sun4i-gpadc.h b/include/linux/mfd/sun4i-gpadc.h
index d7a29f2..509e736 100644
--- a/include/linux/mfd/sun4i-gpadc.h
+++ b/include/linux/mfd/sun4i-gpadc.h
@@ -28,6 +28,7 @@
#define SUN4I_GPADC_CTRL1_TP_MODE_EN BIT(4)
#define SUN4I_GPADC_CTRL1_TP_ADC_SELECT BIT(3)
#define SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(x) (GENMASK(2, 0) & (x))
+#define SUN4I_GPADC_CTRL1_ADC_CHAN_MASK GENMASK(2, 0)
/* TP_CTRL1 bits for sun6i SOCs */
#define SUN6I_GPADC_CTRL1_TOUCH_PAN_CALI_EN BIT(7)
@@ -35,6 +36,7 @@
#define SUN6I_GPADC_CTRL1_TP_MODE_EN BIT(5)
#define SUN6I_GPADC_CTRL1_TP_ADC_SELECT BIT(4)
#define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x) (GENMASK(3, 0) & BIT(x))
+#define SUN6I_GPADC_CTRL1_ADC_CHAN_MASK GENMASK(3, 0)
#define SUN4I_GPADC_CTRL2 0x08
--
2.9.3
^ permalink raw reply related
* [PATCH v8 2/3] mfd: Kconfig: MFD_SUN4I_GPADC depends on !TOUCHSCREN_SUN4I_GPADC
From: Quentin Schulz @ 2016-12-09 10:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161209102236.17655-1-quentin.schulz@free-electrons.com>
MFD_SUN4I_GPADC and TOUCHSCREEN_SUN4I are incompatible (both are drivers
for Allwinner SoCs' ADC). This makes sure TOUCHSCREEN_SUN4I isn't
enabled while MFD_SUN4I_GPADC is enabled.
Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
---
added in v8. I wrongly put the XOR dependance for SUN4I_GPADC and
TOUCHSCREEN_SUN4I instead of for MFD_SUN4I_GPADC and TOUCHSCREEN_SUN4I.
drivers/mfd/Kconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 443ee50..e803884 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -45,6 +45,7 @@ config MFD_SUN4I_GPADC
select MFD_CORE
select REGMAP_MMIO
depends on ARCH_SUNXI || COMPILE_TEST
+ depends on !TOUCHSCREEN_SUN4I
help
Select this to get support for Allwinner SoCs (A10, A13 and A31) ADC.
This driver will only map the hardware interrupt and registers, you
--
2.9.3
^ permalink raw reply related
* [PATCH v8 1/3] ARM: sunxi_defconfig: Add CONFIG_THERMAL_OF
From: Quentin Schulz @ 2016-12-09 10:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161209102236.17655-1-quentin.schulz@free-electrons.com>
This enables CONFIG_THERMAL_OF by default for sunxi_defconfig. It is
required to get Allwinner SoCs' temperature from the GPADC driver.
The Allwinner SoCs all have an ADC that can also act as a touchscreen
controller and a thermal sensor. The first four channels can be used
either for the ADC or the touchscreen and the fifth channel is used for
the thermal sensor.
The thermal sensor requires the IP to be in touchscreen mode to return
correct values. Therefore, if the user is continuously reading the ADC
channel(s), the thermal framework in which the thermal sensor is
registered will switch the IP in touchscreen mode to get a temperature
value and requires a delay of 100ms (because of the mode switching),
then the ADC will switch back to ADC mode and requires also a delay of
100ms. If the ADC readings are critical to user and the SoC temperature
is not, the GPADC driver is capable of not registering the thermal
sensor in the thermal framework and thus, "quicken" the ADC readings. In
most use cases, the SoC temperature is more critical (for cpu throttling
for example or activating cooling devices) than ADC readings, thus it is
now enabled by default.
Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
---
v8:
- more explanatory commit log,
added in v7
arch/arm/configs/sunxi_defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm/configs/sunxi_defconfig b/arch/arm/configs/sunxi_defconfig
index 714da33..8aaeae3 100644
--- a/arch/arm/configs/sunxi_defconfig
+++ b/arch/arm/configs/sunxi_defconfig
@@ -83,6 +83,7 @@ CONFIG_GPIO_SYSFS=y
CONFIG_POWER_SUPPLY=y
CONFIG_AXP20X_POWER=y
CONFIG_THERMAL=y
+CONFIG_THERMAL_OF=y
CONFIG_CPU_THERMAL=y
CONFIG_WATCHDOG=y
CONFIG_SUNXI_WATCHDOG=y
--
2.9.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox