From 66ecbfea762ad28bd108db76775483b491068b92 Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Tue, 22 Apr 2014 23:26:08 +0200 Subject: clk: mvebu: add Orion5x clock driver This commit adds a core clock driver for the Orion5x SoC, with support for the tclk, the CPU frequency and the DDR frequency. All the details about the Sample-At-Reset register were extracted from the U-Boot sources for Orion5x. Note that Orion5x does not have gatable clocks, so this core clock driver is sufficient to support clocking on Orion5x platforms. Signed-off-by: Thomas Petazzoni Acked-by: Sebastian Hesselbarth Link: https://lkml.kernel.org/r/1398202002-28530-5-git-send-email-thomas.petazzoni@free-electrons.com Cc: Mike Turquette Signed-off-by: Jason Cooper --- Documentation/devicetree/bindings/clock/mvebu-core-clock.txt | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/mvebu-core-clock.txt b/Documentation/devicetree/bindings/clock/mvebu-core-clock.txt index 307a503c5db8..dc5ea5b22da9 100644 --- a/Documentation/devicetree/bindings/clock/mvebu-core-clock.txt +++ b/Documentation/devicetree/bindings/clock/mvebu-core-clock.txt @@ -29,6 +29,11 @@ The following is a list of provided IDs and clock names on Kirkwood and Dove: 2 = l2clk (L2 Cache clock derived from CPU0 clock) 3 = ddrclk (DDR controller clock derived from CPU0 clock) +The following is a list of provided IDs and clock names on Orion5x: + 0 = tclk (Internal Bus clock) + 1 = cpuclk (CPU0 clock) + 2 = ddrclk (DDR controller clock derived from CPU0 clock) + Required properties: - compatible : shall be one of the following: "marvell,armada-370-core-clock" - For Armada 370 SoC core clocks @@ -38,6 +43,9 @@ Required properties: "marvell,dove-core-clock" - for Dove SoC core clocks "marvell,kirkwood-core-clock" - for Kirkwood SoC (except mv88f6180) "marvell,mv88f6180-core-clock" - for Kirkwood MV88f6180 SoC + "marvell,mv88f5182-core-clock" - for Orion MV88F5182 SoC + "marvell,mv88f5281-core-clock" - for Orion MV88F5281 SoC + "marvell,mv88f6183-core-clock" - for Orion MV88F6183 SoC - reg : shall be the register address of the Sample-At-Reset (SAR) register - #clock-cells : from common clock binding; shall be set to 1 -- cgit v1.2.3 From 5bcb59260f229d5d829da0e88400441a43d669f0 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 21 Apr 2014 16:26:24 -0500 Subject: ARM: dts: revise kona clock binding document The next patch defines a binding for a new Broadcom SoC that uses Kona style CCUs for its clocks. Update the generic Kona clock binding document so it's more natural to accomodate the definitions of additional SoC families. Specifically: - Define the compatible string values generically, referring to specific per-model values later in the document. - Put the device tree example immediately after the required properties listing, before the tables of SoC-specific values. - Clearly identify the start of the section defining specific values related to the BCM281XX family - Add a list of the specific BCM281XX family compatible strings. - Reword the description of the table slightly. Signed-off-by: Alex Elder Signed-off-by: Mike Turquette --- .../devicetree/bindings/clock/bcm-kona-clock.txt | 87 ++++++++++++---------- 1 file changed, 47 insertions(+), 40 deletions(-) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt b/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt index 56d1f4961075..ebe3ab622c86 100644 --- a/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt +++ b/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt @@ -10,12 +10,12 @@ This binding uses the common clock binding: Required properties: - compatible - Shall have one of the following values: - - "brcm,bcm11351-root-ccu" - - "brcm,bcm11351-aon-ccu" - - "brcm,bcm11351-hub-ccu" - - "brcm,bcm11351-master-ccu" - - "brcm,bcm11351-slave-ccu" + Shall have a value of the form "brcm,--ccu", + where is a Broadcom SoC model number and is + the name of a defined CCU. For example: + "brcm,bcm11351-root-ccu" + The compatible strings used for each supported SoC family + are defined below. - reg Shall define the base and range of the address space containing clock control registers @@ -26,12 +26,48 @@ Required properties: Shall be an ordered list of strings defining the names of the clocks provided by the CCU. +Device tree example: + + slave_ccu: slave_ccu { + compatible = "brcm,bcm11351-slave-ccu"; + reg = <0x3e011000 0x0f00>; + #clock-cells = <1>; + clock-output-names = "uartb", + "uartb2", + "uartb3", + "uartb4"; + }; + + ref_crystal_clk: ref_crystal { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <26000000>; + }; + + uart@3e002000 { + compatible = "brcm,bcm11351-dw-apb-uart", "snps,dw-apb-uart"; + status = "disabled"; + reg = <0x3e002000 0x1000>; + clocks = <&slave_ccu BCM281XX_SLAVE_CCU_UARTB3>; + interrupts = ; + reg-shift = <2>; + reg-io-width = <4>; + }; + +BCM281XX family +--------------- +CCU compatible string values for SoCs in the BCM281XX family are: + "brcm,bcm11351-root-ccu" + "brcm,bcm11351-aon-ccu" + "brcm,bcm11351-hub-ccu" + "brcm,bcm11351-master-ccu" + "brcm,bcm11351-slave-ccu" -BCM281XX family SoCs use Kona CCUs. The following table defines -the set of CCUs and clock specifiers for BCM281XX clocks. When -a clock consumer references a clocks, its symbolic specifier -(rather than its numeric index value) should be used. These -specifiers are defined in "include/dt-bindings/clock/bcm281xx.h". +The following table defines the set of CCUs and clock specifiers for +BCM281XX family clocks. When a clock consumer references a clocks, +its symbolic specifier (rather than its numeric index value) should +be used. These specifiers are defined in: + "include/dt-bindings/clock/bcm281xx.h" CCU Clock Type Index Specifier --- ----- ---- ----- --------- @@ -62,32 +98,3 @@ specifiers are defined in "include/dt-bindings/clock/bcm281xx.h". slave bsc2 peri 7 BCM281XX_SLAVE_CCU_BSC2 slave bsc3 peri 8 BCM281XX_SLAVE_CCU_BSC3 slave pwm peri 9 BCM281XX_SLAVE_CCU_PWM - - -Device tree example: - - slave_ccu: slave_ccu { - compatible = "brcm,bcm11351-slave-ccu"; - reg = <0x3e011000 0x0f00>; - #clock-cells = <1>; - clock-output-names = "uartb", - "uartb2", - "uartb3", - "uartb4"; - }; - - ref_crystal_clk: ref_crystal { - #clock-cells = <0>; - compatible = "fixed-clock"; - clock-frequency = <26000000>; - }; - - uart@3e002000 { - compatible = "brcm,bcm11351-dw-apb-uart", "snps,dw-apb-uart"; - status = "disabled"; - reg = <0x3e002000 0x1000>; - clocks = <&slave_ccu BCM281XX_SLAVE_CCU_UARTB3>; - interrupts = ; - reg-shift = <2>; - reg-io-width = <4>; - }; -- cgit v1.2.3 From 7f2ce16801a0ee20f72608b643b9cafcdec370f1 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 21 Apr 2014 16:26:25 -0500 Subject: ARM: dts: define clock binding for bcm21664 Document the device tree binding for Broadcom BCM28164 clock control units and clocks. This SoC uses Kona CCUs, similar to the BCM281XX SoC family. Signed-off-by: Alex Elder Signed-off-by: Mike Turquette --- .../devicetree/bindings/clock/bcm-kona-clock.txt | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt b/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt index ebe3ab622c86..5286e260fcae 100644 --- a/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt +++ b/Documentation/devicetree/bindings/clock/bcm-kona-clock.txt @@ -98,3 +98,42 @@ be used. These specifiers are defined in: slave bsc2 peri 7 BCM281XX_SLAVE_CCU_BSC2 slave bsc3 peri 8 BCM281XX_SLAVE_CCU_BSC3 slave pwm peri 9 BCM281XX_SLAVE_CCU_PWM + + +BCM21664 family +--------------- +CCU compatible string values for SoCs in the BCM21664 family are: + "brcm,bcm21664-root-ccu" + "brcm,bcm21664-aon-ccu" + "brcm,bcm21664-master-ccu" + "brcm,bcm21664-slave-ccu" + +The following table defines the set of CCUs and clock specifiers for +BCM21664 family clocks. When a clock consumer references a clocks, +its symbolic specifier (rather than its numeric index value) should +be used. These specifiers are defined in: + "include/dt-bindings/clock/bcm21664.h" + + CCU Clock Type Index Specifier + --- ----- ---- ----- --------- + root frac_1m peri 0 BCM21664_ROOT_CCU_FRAC_1M + + aon hub_timer peri 0 BCM21664_AON_CCU_HUB_TIMER + + master sdio1 peri 0 BCM21664_MASTER_CCU_SDIO1 + master sdio2 peri 1 BCM21664_MASTER_CCU_SDIO2 + master sdio3 peri 2 BCM21664_MASTER_CCU_SDIO3 + master sdio4 peri 3 BCM21664_MASTER_CCU_SDIO4 + master sdio1_sleep peri 4 BCM21664_MASTER_CCU_SDIO1_SLEEP + master sdio2_sleep peri 5 BCM21664_MASTER_CCU_SDIO2_SLEEP + master sdio3_sleep peri 6 BCM21664_MASTER_CCU_SDIO3_SLEEP + master sdio4_sleep peri 7 BCM21664_MASTER_CCU_SDIO4_SLEEP + + slave uartb peri 0 BCM21664_SLAVE_CCU_UARTB + slave uartb2 peri 1 BCM21664_SLAVE_CCU_UARTB2 + slave uartb3 peri 2 BCM21664_SLAVE_CCU_UARTB3 + slave uartb4 peri 3 BCM21664_SLAVE_CCU_UARTB4 + slave bsc1 peri 4 BCM21664_SLAVE_CCU_BSC1 + slave bsc2 peri 5 BCM21664_SLAVE_CCU_BSC2 + slave bsc3 peri 6 BCM21664_SLAVE_CCU_BSC3 + slave bsc4 peri 7 BCM21664_SLAVE_CCU_BSC4 -- cgit v1.2.3 From 2d85a713dca8b1c96e78e9f17c6a9bc88d11515a Mon Sep 17 00:00:00 2001 From: Kumar Gala Date: Fri, 4 Apr 2014 11:31:29 -0500 Subject: clk: qcom: Add basic support for APQ8064 global clock controller clocks The APQ8064 and MSM8960 share a significant amount of clock data and code between the two SoCs. Rather than duplicating the data we just add support for a unqiue APQ8064 clock table into the MSM8960 code. For now add just enough clocks to get a basic serial port going on an APQ8064 device. Signed-off-by: Kumar Gala Reviewed-by: Stephen Boyd Signed-off-by: Mike Turquette [mturquette@linaro.org: trivial conflict due to missing ipq8064 support] --- .../devicetree/bindings/clock/qcom,gcc.txt | 1 + drivers/clk/qcom/Kconfig | 4 +-- drivers/clk/qcom/gcc-msm8960.c | 30 +++++++++++++++++++--- 3 files changed, 30 insertions(+), 5 deletions(-) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/qcom,gcc.txt b/Documentation/devicetree/bindings/clock/qcom,gcc.txt index 767401f42871..7b7104e8cb1e 100644 --- a/Documentation/devicetree/bindings/clock/qcom,gcc.txt +++ b/Documentation/devicetree/bindings/clock/qcom,gcc.txt @@ -4,6 +4,7 @@ Qualcomm Global Clock & Reset Controller Binding Required properties : - compatible : shall contain only one of the following: + "qcom,gcc-apq8064" "qcom,gcc-msm8660" "qcom,gcc-msm8960" "qcom,gcc-msm8974" diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 995bcfa021a4..7f696b7d4422 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -13,10 +13,10 @@ config MSM_GCC_8660 i2c, USB, SD/eMMC, etc. config MSM_GCC_8960 - tristate "MSM8960 Global Clock Controller" + tristate "APQ8064/MSM8960 Global Clock Controller" depends on COMMON_CLK_QCOM help - Support for the global clock controller on msm8960 devices. + Support for the global clock controller on apq8064/msm8960 devices. Say Y if you want to use peripheral devices such as UART, SPI, i2c, USB, SD/eMMC, SATA, PCIe, etc. diff --git a/drivers/clk/qcom/gcc-msm8960.c b/drivers/clk/qcom/gcc-msm8960.c index 633b019891bb..8e2b6ddcb8e4 100644 --- a/drivers/clk/qcom/gcc-msm8960.c +++ b/drivers/clk/qcom/gcc-msm8960.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -2868,6 +2868,16 @@ static const struct qcom_reset_map gcc_msm8960_resets[] = { [RIVA_RESET] = { 0x35e0 }, }; +static struct clk_regmap *gcc_apq8064_clks[] = { + [PLL8] = &pll8.clkr, + [PLL8_VOTE] = &pll8_vote, + [GSBI7_UART_SRC] = &gsbi7_uart_src.clkr, + [GSBI7_UART_CLK] = &gsbi7_uart_clk.clkr, + [GSBI7_QUP_SRC] = &gsbi7_qup_src.clkr, + [GSBI7_QUP_CLK] = &gsbi7_qup_clk.clkr, + [GSBI7_H_CLK] = &gsbi7_h_clk.clkr, +}; + static const struct regmap_config gcc_msm8960_regmap_config = { .reg_bits = 32, .reg_stride = 4, @@ -2884,8 +2894,17 @@ static const struct qcom_cc_desc gcc_msm8960_desc = { .num_resets = ARRAY_SIZE(gcc_msm8960_resets), }; +static const struct qcom_cc_desc gcc_apq8064_desc = { + .config = &gcc_msm8960_regmap_config, + .clks = gcc_apq8064_clks, + .num_clks = ARRAY_SIZE(gcc_apq8064_clks), + .resets = gcc_msm8960_resets, + .num_resets = ARRAY_SIZE(gcc_msm8960_resets), +}; + static const struct of_device_id gcc_msm8960_match_table[] = { - { .compatible = "qcom,gcc-msm8960" }, + { .compatible = "qcom,gcc-msm8960", .data = &gcc_msm8960_desc }, + { .compatible = "qcom,gcc-apq8064", .data = &gcc_apq8064_desc }, { } }; MODULE_DEVICE_TABLE(of, gcc_msm8960_match_table); @@ -2894,6 +2913,11 @@ static int gcc_msm8960_probe(struct platform_device *pdev) { struct clk *clk; struct device *dev = &pdev->dev; + const struct of_device_id *match; + + match = of_match_device(gcc_msm8960_match_table, &pdev->dev); + if (!match) + return -EINVAL; /* Temporary until RPM clocks supported */ clk = clk_register_fixed_rate(dev, "cxo", NULL, CLK_IS_ROOT, 19200000); @@ -2904,7 +2928,7 @@ static int gcc_msm8960_probe(struct platform_device *pdev) if (IS_ERR(clk)) return PTR_ERR(clk); - return qcom_cc_probe(pdev, &gcc_msm8960_desc); + return qcom_cc_probe(pdev, match->data); } static int gcc_msm8960_remove(struct platform_device *pdev) -- cgit v1.2.3 From 5efaf09021a5817e5a274aa2d2fad8d92d12ed92 Mon Sep 17 00:00:00 2001 From: Zhangfei Gao Date: Mon, 21 Apr 2014 12:07:27 +0800 Subject: clk: hisi: add clk-hix5hd2.c Signed-off-by: Haifeng Yan Signed-off-by: Zhangfei Gao Signed-off-by: Haojian Zhuang --- .../devicetree/bindings/clock/hix5hd2-clock.txt | 31 +++++++ drivers/clk/Makefile | 1 + drivers/clk/hisilicon/Makefile | 1 + drivers/clk/hisilicon/clk-hix5hd2.c | 101 +++++++++++++++++++++ include/dt-bindings/clock/hix5hd2-clock.h | 58 ++++++++++++ 5 files changed, 192 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/hix5hd2-clock.txt create mode 100644 drivers/clk/hisilicon/clk-hix5hd2.c create mode 100644 include/dt-bindings/clock/hix5hd2-clock.h (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/hix5hd2-clock.txt b/Documentation/devicetree/bindings/clock/hix5hd2-clock.txt new file mode 100644 index 000000000000..7894a64887cb --- /dev/null +++ b/Documentation/devicetree/bindings/clock/hix5hd2-clock.txt @@ -0,0 +1,31 @@ +* Hisilicon Hix5hd2 Clock Controller + +The hix5hd2 clock controller generates and supplies clock to various +controllers within the hix5hd2 SoC. + +Required Properties: + +- compatible: should be "hisilicon,hix5hd2-clock" +- reg: Address and length of the register set +- #clock-cells: Should be <1> + +Each clock is assigned an identifier and client nodes use this identifier +to specify the clock which they consume. + +All these identifier could be found in . + +Examples: + clock: clock@f8a22000 { + compatible = "hisilicon,hix5hd2-clock"; + reg = <0xf8a22000 0x1000>; + #clock-cells = <1>; + }; + + uart0: uart@f8b00000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0xf8b00000 0x1000>; + interrupts = <0 49 4>; + clocks = <&clock HIX5HD2_FIXED_83M>; + clock-names = "apb_pclk"; + status = "disabled"; + }; diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 5f8a28735c96..3e6e577c01db 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_COMMON_CLK_AT91) += at91/ obj-$(CONFIG_ARCH_BCM_MOBILE) += bcm/ obj-$(CONFIG_ARCH_HI3xxx) += hisilicon/ obj-$(CONFIG_ARCH_HIP04) += hisilicon/ +obj-$(CONFIG_ARCH_HIX5HD2) += hisilicon/ obj-$(CONFIG_COMMON_CLK_KEYSTONE) += keystone/ ifeq ($(CONFIG_COMMON_CLK), y) obj-$(CONFIG_ARCH_MMP) += mmp/ diff --git a/drivers/clk/hisilicon/Makefile b/drivers/clk/hisilicon/Makefile index 40b33c6a8257..038c02f4d0e7 100644 --- a/drivers/clk/hisilicon/Makefile +++ b/drivers/clk/hisilicon/Makefile @@ -6,3 +6,4 @@ obj-y += clk.o clkgate-separated.o obj-$(CONFIG_ARCH_HI3xxx) += clk-hi3620.o obj-$(CONFIG_ARCH_HIP04) += clk-hip04.o +obj-$(CONFIG_ARCH_HIX5HD2) += clk-hix5hd2.o diff --git a/drivers/clk/hisilicon/clk-hix5hd2.c b/drivers/clk/hisilicon/clk-hix5hd2.c new file mode 100644 index 000000000000..e5fcfb4e32ef --- /dev/null +++ b/drivers/clk/hisilicon/clk-hix5hd2.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2014 Linaro Ltd. + * Copyright (c) 2014 Hisilicon Limited. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include "clk.h" + +static struct hisi_fixed_rate_clock hix5hd2_fixed_rate_clks[] __initdata = { + { HIX5HD2_FIXED_1200M, "1200m", NULL, CLK_IS_ROOT, 1200000000, }, + { HIX5HD2_FIXED_400M, "400m", NULL, CLK_IS_ROOT, 400000000, }, + { HIX5HD2_FIXED_48M, "48m", NULL, CLK_IS_ROOT, 48000000, }, + { HIX5HD2_FIXED_24M, "24m", NULL, CLK_IS_ROOT, 24000000, }, + { HIX5HD2_FIXED_600M, "600m", NULL, CLK_IS_ROOT, 600000000, }, + { HIX5HD2_FIXED_300M, "300m", NULL, CLK_IS_ROOT, 300000000, }, + { HIX5HD2_FIXED_75M, "75m", NULL, CLK_IS_ROOT, 75000000, }, + { HIX5HD2_FIXED_200M, "200m", NULL, CLK_IS_ROOT, 200000000, }, + { HIX5HD2_FIXED_100M, "100m", NULL, CLK_IS_ROOT, 100000000, }, + { HIX5HD2_FIXED_40M, "40m", NULL, CLK_IS_ROOT, 40000000, }, + { HIX5HD2_FIXED_150M, "150m", NULL, CLK_IS_ROOT, 150000000, }, + { HIX5HD2_FIXED_1728M, "1728m", NULL, CLK_IS_ROOT, 1728000000, }, + { HIX5HD2_FIXED_28P8M, "28p8m", NULL, CLK_IS_ROOT, 28000000, }, + { HIX5HD2_FIXED_432M, "432m", NULL, CLK_IS_ROOT, 432000000, }, + { HIX5HD2_FIXED_345P6M, "345p6m", NULL, CLK_IS_ROOT, 345000000, }, + { HIX5HD2_FIXED_288M, "288m", NULL, CLK_IS_ROOT, 288000000, }, + { HIX5HD2_FIXED_60M, "60m", NULL, CLK_IS_ROOT, 60000000, }, + { HIX5HD2_FIXED_750M, "750m", NULL, CLK_IS_ROOT, 750000000, }, + { HIX5HD2_FIXED_500M, "500m", NULL, CLK_IS_ROOT, 500000000, }, + { HIX5HD2_FIXED_54M, "54m", NULL, CLK_IS_ROOT, 54000000, }, + { HIX5HD2_FIXED_27M, "27m", NULL, CLK_IS_ROOT, 27000000, }, + { HIX5HD2_FIXED_1500M, "1500m", NULL, CLK_IS_ROOT, 1500000000, }, + { HIX5HD2_FIXED_375M, "375m", NULL, CLK_IS_ROOT, 375000000, }, + { HIX5HD2_FIXED_187M, "187m", NULL, CLK_IS_ROOT, 187000000, }, + { HIX5HD2_FIXED_250M, "250m", NULL, CLK_IS_ROOT, 250000000, }, + { HIX5HD2_FIXED_125M, "125m", NULL, CLK_IS_ROOT, 125000000, }, + { HIX5HD2_FIXED_2P02M, "2m", NULL, CLK_IS_ROOT, 2000000, }, + { HIX5HD2_FIXED_50M, "50m", NULL, CLK_IS_ROOT, 50000000, }, + { HIX5HD2_FIXED_25M, "25m", NULL, CLK_IS_ROOT, 25000000, }, + { HIX5HD2_FIXED_83M, "83m", NULL, CLK_IS_ROOT, 83333333, }, +}; + +static const char *sfc_mux_p[] __initconst = { + "24m", "150m", "200m", "100m", "75m", }; +static u32 sfc_mux_table[] = {0, 4, 5, 6, 7}; + +static const char *sdio1_mux_p[] __initconst = { + "75m", "100m", "50m", "15m", }; +static u32 sdio1_mux_table[] = {0, 1, 2, 3}; + +static const char *fephy_mux_p[] __initconst = { "25m", "125m"}; +static u32 fephy_mux_table[] = {0, 1}; + + +static struct hisi_mux_clock hix5hd2_mux_clks[] __initdata = { + { HIX5HD2_SFC_MUX, "sfc_mux", sfc_mux_p, ARRAY_SIZE(sfc_mux_p), + CLK_SET_RATE_PARENT, 0x5c, 8, 3, 0, sfc_mux_table, }, + { HIX5HD2_MMC_MUX, "mmc_mux", sdio1_mux_p, ARRAY_SIZE(sdio1_mux_p), + CLK_SET_RATE_PARENT, 0xa0, 8, 2, 0, sdio1_mux_table, }, + { HIX5HD2_FEPHY_MUX, "fephy_mux", + fephy_mux_p, ARRAY_SIZE(fephy_mux_p), + CLK_SET_RATE_PARENT, 0x120, 8, 2, 0, fephy_mux_table, }, +}; + +static struct hisi_gate_clock hix5hd2_gate_clks[] __initdata = { + /*sfc*/ + { HIX5HD2_SFC_CLK, "clk_sfc", "sfc_mux", + CLK_SET_RATE_PARENT, 0x5c, 0, 0, }, + { HIX5HD2_SFC_RST, "rst_sfc", "clk_sfc", + CLK_SET_RATE_PARENT, 0x5c, 4, CLK_GATE_SET_TO_DISABLE, }, + /*sdio1*/ + { HIX5HD2_MMC_BIU_CLK, "clk_mmc_biu", "200m", + CLK_SET_RATE_PARENT, 0xa0, 0, 0, }, + { HIX5HD2_MMC_CIU_CLK, "clk_mmc_ciu", "mmc_mux", + CLK_SET_RATE_PARENT, 0xa0, 1, 0, }, + { HIX5HD2_MMC_CIU_RST, "rst_mmc_ciu", "clk_mmc_ciu", + CLK_SET_RATE_PARENT, 0xa0, 4, CLK_GATE_SET_TO_DISABLE, }, +}; + +static void __init hix5hd2_clk_init(struct device_node *np) +{ + struct hisi_clock_data *clk_data; + + clk_data = hisi_clk_init(np, HIX5HD2_NR_CLKS); + if (!clk_data) + return; + + hisi_clk_register_fixed_rate(hix5hd2_fixed_rate_clks, + ARRAY_SIZE(hix5hd2_fixed_rate_clks), + clk_data); + hisi_clk_register_mux(hix5hd2_mux_clks, ARRAY_SIZE(hix5hd2_mux_clks), + clk_data); + hisi_clk_register_gate(hix5hd2_gate_clks, + ARRAY_SIZE(hix5hd2_gate_clks), clk_data); +} + +CLK_OF_DECLARE(hix5hd2_clk, "hisilicon,hix5hd2-clock", hix5hd2_clk_init); diff --git a/include/dt-bindings/clock/hix5hd2-clock.h b/include/dt-bindings/clock/hix5hd2-clock.h new file mode 100644 index 000000000000..aad579a75802 --- /dev/null +++ b/include/dt-bindings/clock/hix5hd2-clock.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2014 Linaro Ltd. + * Copyright (c) 2014 Hisilicon Limited. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#ifndef __DTS_HIX5HD2_CLOCK_H +#define __DTS_HIX5HD2_CLOCK_H + +/* fixed rate */ +#define HIX5HD2_FIXED_1200M 1 +#define HIX5HD2_FIXED_400M 2 +#define HIX5HD2_FIXED_48M 3 +#define HIX5HD2_FIXED_24M 4 +#define HIX5HD2_FIXED_600M 5 +#define HIX5HD2_FIXED_300M 6 +#define HIX5HD2_FIXED_75M 7 +#define HIX5HD2_FIXED_200M 8 +#define HIX5HD2_FIXED_100M 9 +#define HIX5HD2_FIXED_40M 10 +#define HIX5HD2_FIXED_150M 11 +#define HIX5HD2_FIXED_1728M 12 +#define HIX5HD2_FIXED_28P8M 13 +#define HIX5HD2_FIXED_432M 14 +#define HIX5HD2_FIXED_345P6M 15 +#define HIX5HD2_FIXED_288M 16 +#define HIX5HD2_FIXED_60M 17 +#define HIX5HD2_FIXED_750M 18 +#define HIX5HD2_FIXED_500M 19 +#define HIX5HD2_FIXED_54M 20 +#define HIX5HD2_FIXED_27M 21 +#define HIX5HD2_FIXED_1500M 22 +#define HIX5HD2_FIXED_375M 23 +#define HIX5HD2_FIXED_187M 24 +#define HIX5HD2_FIXED_250M 25 +#define HIX5HD2_FIXED_125M 26 +#define HIX5HD2_FIXED_2P02M 27 +#define HIX5HD2_FIXED_50M 28 +#define HIX5HD2_FIXED_25M 29 +#define HIX5HD2_FIXED_83M 30 + +/* mux clocks */ +#define HIX5HD2_SFC_MUX 64 +#define HIX5HD2_MMC_MUX 65 +#define HIX5HD2_FEPHY_MUX 66 + +/* gate clocks */ +#define HIX5HD2_SFC_RST 128 +#define HIX5HD2_SFC_CLK 129 +#define HIX5HD2_MMC_CIU_CLK 130 +#define HIX5HD2_MMC_BIU_CLK 131 +#define HIX5HD2_MMC_CIU_RST 132 + +#define HIX5HD2_NR_CLKS 256 +#endif /* __DTS_HIX5HD2_CLOCK_H */ -- cgit v1.2.3 From b4b3bfd0b319b206d4f2fe3380f3d7d594e6e414 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 14 Apr 2014 19:00:40 +0200 Subject: clk: Improve clock-indices binding documentation Improve the wording for the clock-indices binding documentation. Also replace "empty nodes" by "empty strings", as reported before by Sergei Shtylyov. Signed-off-by: Geert Uytterhoeven Acked-by: Laurent Pinchart Acked-by: Simon Horman Signed-off-by: Mike Turquette --- Documentation/devicetree/bindings/clock/clock-bindings.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/clock-bindings.txt b/Documentation/devicetree/bindings/clock/clock-bindings.txt index 700e7aac3717..f15787817d6b 100644 --- a/Documentation/devicetree/bindings/clock/clock-bindings.txt +++ b/Documentation/devicetree/bindings/clock/clock-bindings.txt @@ -44,10 +44,9 @@ For example: clocks by index. The names should reflect the clock output signal names for the device. -clock-indices: If the identifyng number for the clocks in the node - is not linear from zero, then the this mapping allows - the mapping of identifiers into the clock-output-names - array. +clock-indices: If the identifying number for the clocks in the node + is not linear from zero, then this allows the mapping of + identifiers into the clock-output-names array. For example, if we have two clocks <&oscillator 1> and <&oscillator 3>: @@ -58,7 +57,7 @@ For example, if we have two clocks <&oscillator 1> and <&oscillator 3>: clock-output-names = "clka", "clkb"; } - This ensures we do not have any empty nodes in clock-output-names + This ensures we do not have any empty strings in clock-output-names ==Clock consumers== -- cgit v1.2.3 From 7b42a997bfb93c6ae0709f34ec8e2860757804b5 Mon Sep 17 00:00:00 2001 From: Simon Horman Date: Fri, 18 Apr 2014 08:05:50 +0900 Subject: clk: shmobile: r8a7779: Add clocks support The R8A7779 SoC has several clocks that are too custom to be supported in a generic driver. Those clocks are all fixed rate clocks with multiplier and divisor set according to boot mode configuration. Based on work for R-Car Gen2 SoCs by Laurent Pinchart. Cc: devicetree@vger.kernel.org Acked-by: Laurent Pinchart Signed-off-by: Simon Horman Signed-off-by: Mike Turquette --- .../bindings/clock/renesas,r8a7779-cpg-clocks.txt | 27 ++++ drivers/clk/shmobile/Makefile | 1 + drivers/clk/shmobile/clk-r8a7779.c | 180 +++++++++++++++++++++ include/linux/clk/shmobile.h | 3 + 4 files changed, 211 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/renesas,r8a7779-cpg-clocks.txt create mode 100644 drivers/clk/shmobile/clk-r8a7779.c (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/renesas,r8a7779-cpg-clocks.txt b/Documentation/devicetree/bindings/clock/renesas,r8a7779-cpg-clocks.txt new file mode 100644 index 000000000000..ed3c8cb12f4e --- /dev/null +++ b/Documentation/devicetree/bindings/clock/renesas,r8a7779-cpg-clocks.txt @@ -0,0 +1,27 @@ +* Renesas R8A7779 Clock Pulse Generator (CPG) + +The CPG generates core clocks for the R8A7779. It includes one PLL and +several fixed ratio dividers + +Required Properties: + + - compatible: Must be "renesas,r8a7779-cpg-clocks" + - reg: Base address and length of the memory resource used by the CPG + + - clocks: Reference to the parent clock + - #clock-cells: Must be 1 + - clock-output-names: The names of the clocks. Supported clocks are "plla", + "z", "zs", "s", "s1", "p", "b", "out". + + +Example +------- + + cpg_clocks: cpg_clocks@ffc80000 { + compatible = "renesas,r8a7779-cpg-clocks"; + reg = <0 0xffc80000 0 0x30>; + clocks = <&extal_clk>; + #clock-cells = <1>; + clock-output-names = "plla", "z", "zs", "s", "s1", "p", + "b", "out"; + }; diff --git a/drivers/clk/shmobile/Makefile b/drivers/clk/shmobile/Makefile index 5404cb931ebf..bdf342daefa5 100644 --- a/drivers/clk/shmobile/Makefile +++ b/drivers/clk/shmobile/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_ARCH_EMEV2) += clk-emev2.o obj-$(CONFIG_ARCH_R7S72100) += clk-rz.o +obj-$(CONFIG_ARCH_R8A7779) += clk-r8a7779.o obj-$(CONFIG_ARCH_R8A7790) += clk-rcar-gen2.o obj-$(CONFIG_ARCH_R8A7791) += clk-rcar-gen2.o obj-$(CONFIG_ARCH_SHMOBILE_MULTI) += clk-div6.o diff --git a/drivers/clk/shmobile/clk-r8a7779.c b/drivers/clk/shmobile/clk-r8a7779.c new file mode 100644 index 000000000000..652ecacb6daf --- /dev/null +++ b/drivers/clk/shmobile/clk-r8a7779.c @@ -0,0 +1,180 @@ +/* + * r8a7779 Core CPG Clocks + * + * Copyright (C) 2013, 2014 Horms Solutions Ltd. + * + * Contact: Simon Horman + * + * 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 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define CPG_NUM_CLOCKS (R8A7779_CLK_OUT + 1) + +struct r8a7779_cpg { + struct clk_onecell_data data; + spinlock_t lock; + void __iomem *reg; +}; + +/* ----------------------------------------------------------------------------- + * CPG Clock Data + */ + +/* + * MD1 = 1 MD1 = 0 + * (PLLA = 1500) (PLLA = 1600) + * (MHz) (MHz) + *------------------------------------------------+-------------------- + * clkz 1000 (2/3) 800 (1/2) + * clkzs 250 (1/6) 200 (1/8) + * clki 750 (1/2) 800 (1/2) + * clks 250 (1/6) 200 (1/8) + * clks1 125 (1/12) 100 (1/16) + * clks3 187.5 (1/8) 200 (1/8) + * clks4 93.7 (1/16) 100 (1/16) + * clkp 62.5 (1/24) 50 (1/32) + * clkg 62.5 (1/24) 66.6 (1/24) + * clkb, CLKOUT + * (MD2 = 0) 62.5 (1/24) 66.6 (1/24) + * (MD2 = 1) 41.6 (1/36) 50 (1/32) + */ + +#define CPG_CLK_CONFIG_INDEX(md) (((md) & (BIT(2)|BIT(1))) >> 1) + +struct cpg_clk_config { + unsigned int z_mult; + unsigned int z_div; + unsigned int zs_and_s_div; + unsigned int s1_div; + unsigned int p_div; + unsigned int b_and_out_div; +}; + +static const struct cpg_clk_config cpg_clk_configs[4] __initconst = { + { 1, 2, 8, 16, 32, 24 }, + { 2, 3, 6, 12, 24, 24 }, + { 1, 2, 8, 16, 32, 32 }, + { 2, 3, 6, 12, 24, 36 }, +}; + +/* + * MD PLLA Ratio + * 12 11 + *------------------------ + * 0 0 x42 + * 0 1 x48 + * 1 0 x56 + * 1 1 x64 + */ + +#define CPG_PLLA_MULT_INDEX(md) (((md) & (BIT(12)|BIT(11))) >> 11) + +static const unsigned int cpg_plla_mult[4] __initconst = { 42, 48, 56, 64 }; + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +static u32 cpg_mode __initdata; + +static struct clk * __init +r8a7779_cpg_register_clock(struct device_node *np, struct r8a7779_cpg *cpg, + const struct cpg_clk_config *config, + unsigned int plla_mult, const char *name) +{ + const char *parent_name = "plla"; + unsigned int mult = 1; + unsigned int div = 1; + + if (!strcmp(name, "plla")) { + parent_name = of_clk_get_parent_name(np, 0); + mult = plla_mult; + } else if (!strcmp(name, "z")) { + div = config->z_div; + mult = config->z_mult; + } else if (!strcmp(name, "zs") || !strcmp(name, "s")) { + div = config->zs_and_s_div; + } else if (!strcmp(name, "s1")) { + div = config->s1_div; + } else if (!strcmp(name, "p")) { + div = config->p_div; + } else if (!strcmp(name, "b") || !strcmp(name, "out")) { + div = config->b_and_out_div; + } else { + return ERR_PTR(-EINVAL); + } + + return clk_register_fixed_factor(NULL, name, parent_name, 0, mult, div); +} + +static void __init r8a7779_cpg_clocks_init(struct device_node *np) +{ + const struct cpg_clk_config *config; + struct r8a7779_cpg *cpg; + struct clk **clks; + unsigned int i, plla_mult; + int num_clks; + + num_clks = of_property_count_strings(np, "clock-output-names"); + if (num_clks < 0) { + pr_err("%s: failed to count clocks\n", __func__); + return; + } + + cpg = kzalloc(sizeof(*cpg), GFP_KERNEL); + clks = kzalloc(CPG_NUM_CLOCKS * sizeof(*clks), GFP_KERNEL); + if (cpg == NULL || clks == NULL) { + /* We're leaking memory on purpose, there's no point in cleaning + * up as the system won't boot anyway. + */ + return; + } + + spin_lock_init(&cpg->lock); + + cpg->data.clks = clks; + cpg->data.clk_num = num_clks; + + config = &cpg_clk_configs[CPG_CLK_CONFIG_INDEX(cpg_mode)]; + plla_mult = cpg_plla_mult[CPG_PLLA_MULT_INDEX(cpg_mode)]; + + for (i = 0; i < num_clks; ++i) { + const char *name; + struct clk *clk; + + of_property_read_string_index(np, "clock-output-names", i, + &name); + + clk = r8a7779_cpg_register_clock(np, cpg, config, + plla_mult, name); + if (IS_ERR(clk)) + pr_err("%s: failed to register %s %s clock (%ld)\n", + __func__, np->name, name, PTR_ERR(clk)); + else + cpg->data.clks[i] = clk; + } + + of_clk_add_provider(np, of_clk_src_onecell_get, &cpg->data); +} +CLK_OF_DECLARE(r8a7779_cpg_clks, "renesas,r8a7779-cpg-clocks", + r8a7779_cpg_clocks_init); + +void __init r8a7779_clocks_init(u32 mode) +{ + cpg_mode = mode; + + of_clk_init(NULL); +} diff --git a/include/linux/clk/shmobile.h b/include/linux/clk/shmobile.h index f9bf080a1123..9f8a14041dd5 100644 --- a/include/linux/clk/shmobile.h +++ b/include/linux/clk/shmobile.h @@ -1,7 +1,9 @@ /* * Copyright 2013 Ideas On Board SPRL + * Copyright 2013, 2014 Horms Solutions Ltd. * * Contact: Laurent Pinchart + * Contact: Simon Horman * * 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 @@ -14,6 +16,7 @@ #include +void r8a7779_clocks_init(u32 mode); void rcar_gen2_clocks_init(u32 mode); #endif -- cgit v1.2.3 From 5483bf698f42a6f0467463b064084eb888298232 Mon Sep 17 00:00:00 2001 From: Simon Horman Date: Fri, 18 Apr 2014 08:05:51 +0900 Subject: clk: shmobile: r8a7779: Add MSTP clock support Add MSTP clock support for the r8a7779 SoC The only change required appears to be to note a new compatibility string in the bindings. Cc: devicetree@vger.kernel.org Acked-by: Laurent Pinchart Signed-off-by: Simon Horman Signed-off-by: Mike Turquette --- Documentation/devicetree/bindings/clock/renesas,cpg-mstp-clocks.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/renesas,cpg-mstp-clocks.txt b/Documentation/devicetree/bindings/clock/renesas,cpg-mstp-clocks.txt index 02a25d99ca61..30df825d72ef 100644 --- a/Documentation/devicetree/bindings/clock/renesas,cpg-mstp-clocks.txt +++ b/Documentation/devicetree/bindings/clock/renesas,cpg-mstp-clocks.txt @@ -10,6 +10,7 @@ index in the group, from 0 to 31. Required Properties: - compatible: Must be one of the following + - "renesas,r8a7779-mstp-clocks" for R8A7779 (R-Car H1) MSTP gate clocks - "renesas,r8a7790-mstp-clocks" for R8A7790 (R-Car H2) MSTP gate clocks - "renesas,r8a7791-mstp-clocks" for R8A7791 (R-Car M2) MSTP gate clocks - "renesas,cpg-mstp-clock" for generic MSTP gate clocks -- cgit v1.2.3 From 409f01395b9afac302b943926466cd8dbca61d0a Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Fri, 9 May 2014 00:01:50 -0300 Subject: Documentation: clock: fixed-clock: Remove unsupported 'gpios' property Remove the 'gpios' property from the documentation as this is something that the current fixed clock driver does not handle. Signed-off-by: Fabio Estevam Signed-off-by: Mike Turquette --- Documentation/devicetree/bindings/clock/fixed-clock.txt | 1 - 1 file changed, 1 deletion(-) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/fixed-clock.txt b/Documentation/devicetree/bindings/clock/fixed-clock.txt index 48ea0ad8ad46..0641a663ad69 100644 --- a/Documentation/devicetree/bindings/clock/fixed-clock.txt +++ b/Documentation/devicetree/bindings/clock/fixed-clock.txt @@ -12,7 +12,6 @@ Required properties: Optional properties: - clock-accuracy : accuracy of clock in ppb (parts per billion). Should be a single cell. -- gpios : From common gpio binding; gpio connection to clock enable pin. - clock-output-names : From common clock binding. Example: -- cgit v1.2.3 From c675a00c2d666c8e90da335eafbbae81201d53f7 Mon Sep 17 00:00:00 2001 From: Anders Berg Date: Thu, 15 May 2014 15:42:25 +0200 Subject: clk: Add clock driver for AXM55xx SoC Add clk driver to support clock blocks found on the AXM55xx devices. The driver provides clock implementations for three different types of clock devices on the AXM55xx device: PLL clock, a clock divider and a clock mux. Signed-off-by: Anders Berg Cc: Mark Rutland Signed-off-by: Mike Turquette --- .../devicetree/bindings/clock/lsi,axm5516-clks.txt | 29 + drivers/clk/Makefile | 1 + drivers/clk/clk-axm5516.c | 615 +++++++++++++++++++++ 3 files changed, 645 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/lsi,axm5516-clks.txt create mode 100644 drivers/clk/clk-axm5516.c (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/lsi,axm5516-clks.txt b/Documentation/devicetree/bindings/clock/lsi,axm5516-clks.txt new file mode 100644 index 000000000000..3ce97cfe999b --- /dev/null +++ b/Documentation/devicetree/bindings/clock/lsi,axm5516-clks.txt @@ -0,0 +1,29 @@ +AXM5516 clock driver bindings +----------------------------- + +Required properties : +- compatible : shall contain "lsi,axm5516-clks" +- reg : shall contain base register location and length +- #clock-cells : shall contain 1 + +The consumer specifies the desired clock by having the clock ID in its "clocks" +phandle cell. See for the list of +supported clock IDs. + +Example: + + clks: clock-controller@2010020000 { + compatible = "lsi,axm5516-clks"; + #clock-cells = <1>; + reg = <0x20 0x10020000 0 0x20000>; + }; + + serial0: uart@2010080000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x20 0x10080000 0 0x1000>; + interrupts = ; + clocks = <&clks AXXIA_CLK_PER>; + clock-names = "apb_pclk"; + }; + }; + diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 3e6e577c01db..f30357aa5471 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_COMMON_CLK) += clk-composite.o # hardware specific clock types # please keep this section sorted lexicographically by file/directory path name obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o +obj-$(CONFIG_ARCH_AXXIA) += clk-axm5516.o obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o obj-$(CONFIG_ARCH_EFM32) += clk-efm32gg.o obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o diff --git a/drivers/clk/clk-axm5516.c b/drivers/clk/clk-axm5516.c new file mode 100644 index 000000000000..d2f1e119b450 --- /dev/null +++ b/drivers/clk/clk-axm5516.c @@ -0,0 +1,615 @@ +/* + * drivers/clk/clk-axm5516.c + * + * Provides clock implementations for three different types of clock devices on + * the Axxia device: PLL clock, a clock divider and a clock mux. + * + * Copyright (C) 2014 LSI Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * struct axxia_clk - Common struct to all Axxia clocks. + * @hw: clk_hw for the common clk framework + * @regmap: Regmap for the clock control registers + */ +struct axxia_clk { + struct clk_hw hw; + struct regmap *regmap; +}; +#define to_axxia_clk(_hw) container_of(_hw, struct axxia_clk, hw) + +/** + * struct axxia_pllclk - Axxia PLL generated clock. + * @aclk: Common struct + * @reg: Offset into regmap for PLL control register + */ +struct axxia_pllclk { + struct axxia_clk aclk; + u32 reg; +}; +#define to_axxia_pllclk(_aclk) container_of(_aclk, struct axxia_pllclk, aclk) + +/** + * axxia_pllclk_recalc - Calculate the PLL generated clock rate given the + * parent clock rate. + */ +static unsigned long +axxia_pllclk_recalc(struct clk_hw *hw, unsigned long parent_rate) +{ + struct axxia_clk *aclk = to_axxia_clk(hw); + struct axxia_pllclk *pll = to_axxia_pllclk(aclk); + unsigned long rate, fbdiv, refdiv, postdiv; + u32 control; + + regmap_read(aclk->regmap, pll->reg, &control); + postdiv = ((control >> 0) & 0xf) + 1; + fbdiv = ((control >> 4) & 0xfff) + 3; + refdiv = ((control >> 16) & 0x1f) + 1; + rate = (parent_rate / (refdiv * postdiv)) * fbdiv; + + return rate; +} + +static const struct clk_ops axxia_pllclk_ops = { + .recalc_rate = axxia_pllclk_recalc, +}; + +/** + * struct axxia_divclk - Axxia clock divider + * @aclk: Common struct + * @reg: Offset into regmap for PLL control register + * @shift: Bit position for divider value + * @width: Number of bits in divider value + */ +struct axxia_divclk { + struct axxia_clk aclk; + u32 reg; + u32 shift; + u32 width; +}; +#define to_axxia_divclk(_aclk) container_of(_aclk, struct axxia_divclk, aclk) + +/** + * axxia_divclk_recalc_rate - Calculate clock divider output rage + */ +static unsigned long +axxia_divclk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct axxia_clk *aclk = to_axxia_clk(hw); + struct axxia_divclk *divclk = to_axxia_divclk(aclk); + u32 ctrl, div; + + regmap_read(aclk->regmap, divclk->reg, &ctrl); + div = 1 + ((ctrl >> divclk->shift) & ((1 << divclk->width)-1)); + + return parent_rate / div; +} + +static const struct clk_ops axxia_divclk_ops = { + .recalc_rate = axxia_divclk_recalc_rate, +}; + +/** + * struct axxia_clkmux - Axxia clock mux + * @aclk: Common struct + * @reg: Offset into regmap for PLL control register + * @shift: Bit position for selection value + * @width: Number of bits in selection value + */ +struct axxia_clkmux { + struct axxia_clk aclk; + u32 reg; + u32 shift; + u32 width; +}; +#define to_axxia_clkmux(_aclk) container_of(_aclk, struct axxia_clkmux, aclk) + +/** + * axxia_clkmux_get_parent - Return the index of selected parent clock + */ +static u8 axxia_clkmux_get_parent(struct clk_hw *hw) +{ + struct axxia_clk *aclk = to_axxia_clk(hw); + struct axxia_clkmux *mux = to_axxia_clkmux(aclk); + u32 ctrl, parent; + + regmap_read(aclk->regmap, mux->reg, &ctrl); + parent = (ctrl >> mux->shift) & ((1 << mux->width) - 1); + + return (u8) parent; +} + +static const struct clk_ops axxia_clkmux_ops = { + .get_parent = axxia_clkmux_get_parent, +}; + + +/* + * PLLs + */ + +static struct axxia_pllclk clk_fab_pll = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_fab_pll", + .parent_names = (const char *[]){ + "clk_ref0" + }, + .num_parents = 1, + .ops = &axxia_pllclk_ops, + }, + .reg = 0x01800, +}; + +static struct axxia_pllclk clk_cpu_pll = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_cpu_pll", + .parent_names = (const char *[]){ + "clk_ref0" + }, + .num_parents = 1, + .ops = &axxia_pllclk_ops, + }, + .reg = 0x02000, +}; + +static struct axxia_pllclk clk_sys_pll = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_sys_pll", + .parent_names = (const char *[]){ + "clk_ref0" + }, + .num_parents = 1, + .ops = &axxia_pllclk_ops, + }, + .reg = 0x02800, +}; + +static struct axxia_pllclk clk_sm0_pll = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_sm0_pll", + .parent_names = (const char *[]){ + "clk_ref2" + }, + .num_parents = 1, + .ops = &axxia_pllclk_ops, + }, + .reg = 0x03000, +}; + +static struct axxia_pllclk clk_sm1_pll = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_sm1_pll", + .parent_names = (const char *[]){ + "clk_ref1" + }, + .num_parents = 1, + .ops = &axxia_pllclk_ops, + }, + .reg = 0x03800, +}; + +/* + * Clock dividers + */ + +static struct axxia_divclk clk_cpu0_div = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_cpu0_div", + .parent_names = (const char *[]){ + "clk_cpu_pll" + }, + .num_parents = 1, + .ops = &axxia_divclk_ops, + }, + .reg = 0x10008, + .shift = 0, + .width = 4, +}; + +static struct axxia_divclk clk_cpu1_div = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_cpu1_div", + .parent_names = (const char *[]){ + "clk_cpu_pll" + }, + .num_parents = 1, + .ops = &axxia_divclk_ops, + }, + .reg = 0x10008, + .shift = 4, + .width = 4, +}; + +static struct axxia_divclk clk_cpu2_div = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_cpu2_div", + .parent_names = (const char *[]){ + "clk_cpu_pll" + }, + .num_parents = 1, + .ops = &axxia_divclk_ops, + }, + .reg = 0x10008, + .shift = 8, + .width = 4, +}; + +static struct axxia_divclk clk_cpu3_div = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_cpu3_div", + .parent_names = (const char *[]){ + "clk_cpu_pll" + }, + .num_parents = 1, + .ops = &axxia_divclk_ops, + }, + .reg = 0x10008, + .shift = 12, + .width = 4, +}; + +static struct axxia_divclk clk_nrcp_div = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_nrcp_div", + .parent_names = (const char *[]){ + "clk_sys_pll" + }, + .num_parents = 1, + .ops = &axxia_divclk_ops, + }, + .reg = 0x1000c, + .shift = 0, + .width = 4, +}; + +static struct axxia_divclk clk_sys_div = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_sys_div", + .parent_names = (const char *[]){ + "clk_sys_pll" + }, + .num_parents = 1, + .ops = &axxia_divclk_ops, + }, + .reg = 0x1000c, + .shift = 4, + .width = 4, +}; + +static struct axxia_divclk clk_fab_div = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_fab_div", + .parent_names = (const char *[]){ + "clk_fab_pll" + }, + .num_parents = 1, + .ops = &axxia_divclk_ops, + }, + .reg = 0x1000c, + .shift = 8, + .width = 4, +}; + +static struct axxia_divclk clk_per_div = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_per_div", + .parent_names = (const char *[]){ + "clk_sm1_pll" + }, + .num_parents = 1, + .flags = CLK_IS_BASIC, + .ops = &axxia_divclk_ops, + }, + .reg = 0x1000c, + .shift = 12, + .width = 4, +}; + +static struct axxia_divclk clk_mmc_div = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_mmc_div", + .parent_names = (const char *[]){ + "clk_sm1_pll" + }, + .num_parents = 1, + .flags = CLK_IS_BASIC, + .ops = &axxia_divclk_ops, + }, + .reg = 0x1000c, + .shift = 16, + .width = 4, +}; + +/* + * Clock MUXes + */ + +static struct axxia_clkmux clk_cpu0_mux = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_cpu0", + .parent_names = (const char *[]){ + "clk_ref0", + "clk_cpu_pll", + "clk_cpu0_div", + "clk_cpu0_div" + }, + .num_parents = 4, + .ops = &axxia_clkmux_ops, + }, + .reg = 0x10000, + .shift = 0, + .width = 2, +}; + +static struct axxia_clkmux clk_cpu1_mux = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_cpu1", + .parent_names = (const char *[]){ + "clk_ref0", + "clk_cpu_pll", + "clk_cpu1_div", + "clk_cpu1_div" + }, + .num_parents = 4, + .ops = &axxia_clkmux_ops, + }, + .reg = 0x10000, + .shift = 2, + .width = 2, +}; + +static struct axxia_clkmux clk_cpu2_mux = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_cpu2", + .parent_names = (const char *[]){ + "clk_ref0", + "clk_cpu_pll", + "clk_cpu2_div", + "clk_cpu2_div" + }, + .num_parents = 4, + .ops = &axxia_clkmux_ops, + }, + .reg = 0x10000, + .shift = 4, + .width = 2, +}; + +static struct axxia_clkmux clk_cpu3_mux = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_cpu3", + .parent_names = (const char *[]){ + "clk_ref0", + "clk_cpu_pll", + "clk_cpu3_div", + "clk_cpu3_div" + }, + .num_parents = 4, + .ops = &axxia_clkmux_ops, + }, + .reg = 0x10000, + .shift = 6, + .width = 2, +}; + +static struct axxia_clkmux clk_nrcp_mux = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_nrcp", + .parent_names = (const char *[]){ + "clk_ref0", + "clk_sys_pll", + "clk_nrcp_div", + "clk_nrcp_div" + }, + .num_parents = 4, + .ops = &axxia_clkmux_ops, + }, + .reg = 0x10004, + .shift = 0, + .width = 2, +}; + +static struct axxia_clkmux clk_sys_mux = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_sys", + .parent_names = (const char *[]){ + "clk_ref0", + "clk_sys_pll", + "clk_sys_div", + "clk_sys_div" + }, + .num_parents = 4, + .ops = &axxia_clkmux_ops, + }, + .reg = 0x10004, + .shift = 2, + .width = 2, +}; + +static struct axxia_clkmux clk_fab_mux = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_fab", + .parent_names = (const char *[]){ + "clk_ref0", + "clk_fab_pll", + "clk_fab_div", + "clk_fab_div" + }, + .num_parents = 4, + .ops = &axxia_clkmux_ops, + }, + .reg = 0x10004, + .shift = 4, + .width = 2, +}; + +static struct axxia_clkmux clk_per_mux = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_per", + .parent_names = (const char *[]){ + "clk_ref1", + "clk_per_div" + }, + .num_parents = 2, + .ops = &axxia_clkmux_ops, + }, + .reg = 0x10004, + .shift = 6, + .width = 1, +}; + +static struct axxia_clkmux clk_mmc_mux = { + .aclk.hw.init = &(struct clk_init_data){ + .name = "clk_mmc", + .parent_names = (const char *[]){ + "clk_ref1", + "clk_mmc_div" + }, + .num_parents = 2, + .ops = &axxia_clkmux_ops, + }, + .reg = 0x10004, + .shift = 9, + .width = 1, +}; + +/* Table of all supported clocks indexed by the clock identifiers from the + * device tree binding + */ +static struct axxia_clk *axmclk_clocks[] = { + [AXXIA_CLK_FAB_PLL] = &clk_fab_pll.aclk, + [AXXIA_CLK_CPU_PLL] = &clk_cpu_pll.aclk, + [AXXIA_CLK_SYS_PLL] = &clk_sys_pll.aclk, + [AXXIA_CLK_SM0_PLL] = &clk_sm0_pll.aclk, + [AXXIA_CLK_SM1_PLL] = &clk_sm1_pll.aclk, + [AXXIA_CLK_FAB_DIV] = &clk_fab_div.aclk, + [AXXIA_CLK_SYS_DIV] = &clk_sys_div.aclk, + [AXXIA_CLK_NRCP_DIV] = &clk_nrcp_div.aclk, + [AXXIA_CLK_CPU0_DIV] = &clk_cpu0_div.aclk, + [AXXIA_CLK_CPU1_DIV] = &clk_cpu1_div.aclk, + [AXXIA_CLK_CPU2_DIV] = &clk_cpu2_div.aclk, + [AXXIA_CLK_CPU3_DIV] = &clk_cpu3_div.aclk, + [AXXIA_CLK_PER_DIV] = &clk_per_div.aclk, + [AXXIA_CLK_MMC_DIV] = &clk_mmc_div.aclk, + [AXXIA_CLK_FAB] = &clk_fab_mux.aclk, + [AXXIA_CLK_SYS] = &clk_sys_mux.aclk, + [AXXIA_CLK_NRCP] = &clk_nrcp_mux.aclk, + [AXXIA_CLK_CPU0] = &clk_cpu0_mux.aclk, + [AXXIA_CLK_CPU1] = &clk_cpu1_mux.aclk, + [AXXIA_CLK_CPU2] = &clk_cpu2_mux.aclk, + [AXXIA_CLK_CPU3] = &clk_cpu3_mux.aclk, + [AXXIA_CLK_PER] = &clk_per_mux.aclk, + [AXXIA_CLK_MMC] = &clk_mmc_mux.aclk, +}; + +static const struct regmap_config axmclk_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1fffc, + .fast_io = true, +}; + +static const struct of_device_id axmclk_match_table[] = { + { .compatible = "lsi,axm5516-clks" }, + { } +}; +MODULE_DEVICE_TABLE(of, axmclk_match_table); + +struct axmclk_priv { + struct clk_onecell_data onecell; + struct clk *clks[]; +}; + +static int axmclk_probe(struct platform_device *pdev) +{ + void __iomem *base; + struct resource *res; + int i, ret; + struct device *dev = &pdev->dev; + struct clk *clk; + struct regmap *regmap; + size_t num_clks; + struct axmclk_priv *priv; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(dev, base, &axmclk_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + num_clks = ARRAY_SIZE(axmclk_clocks); + pr_info("axmclk: supporting %u clocks\n", num_clks); + priv = devm_kzalloc(dev, sizeof(*priv) + sizeof(*priv->clks) * num_clks, + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->onecell.clks = priv->clks; + priv->onecell.clk_num = num_clks; + + /* Update each entry with the allocated regmap and register the clock + * with the common clock framework + */ + for (i = 0; i < num_clks; i++) { + axmclk_clocks[i]->regmap = regmap; + clk = devm_clk_register(dev, &axmclk_clocks[i]->hw); + if (IS_ERR(clk)) + return PTR_ERR(clk); + priv->clks[i] = clk; + } + + ret = of_clk_add_provider(dev->of_node, + of_clk_src_onecell_get, &priv->onecell); + + return ret; +} + +static int axmclk_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + return 0; +} + +static struct platform_driver axmclk_driver = { + .probe = axmclk_probe, + .remove = axmclk_remove, + .driver = { + .name = "clk-axm5516", + .owner = THIS_MODULE, + .of_match_table = axmclk_match_table, + }, +}; + +static int __init axmclk_init(void) +{ + return platform_driver_register(&axmclk_driver); +} +core_initcall(axmclk_init); + +static void __exit axmclk_exit(void) +{ + platform_driver_unregister(&axmclk_driver); +} +module_exit(axmclk_exit); + +MODULE_DESCRIPTION("AXM5516 clock driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:clk-axm5516"); -- cgit v1.2.3 From 1923ca92a618b6107d3a44b5c9f9360b104dcddb Mon Sep 17 00:00:00 2001 From: Ulrich Hecht Date: Fri, 23 May 2014 15:27:15 +0200 Subject: clk: shmobile: Add R8A7740-specific clock support Driver for the R8A7740's clocks that are too specific to be supported by a generic driver. Signed-off-by: Ulrich Hecht Acked-by: Geert Uytterhoeven Acked-by: Laurent Pinchart Signed-off-by: Mike Turquette --- .../bindings/clock/renesas,r8a7740-cpg-clocks.txt | 41 +++++ drivers/clk/shmobile/Makefile | 1 + drivers/clk/shmobile/clk-r8a7740.c | 199 +++++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/renesas,r8a7740-cpg-clocks.txt create mode 100644 drivers/clk/shmobile/clk-r8a7740.c (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/renesas,r8a7740-cpg-clocks.txt b/Documentation/devicetree/bindings/clock/renesas,r8a7740-cpg-clocks.txt new file mode 100644 index 000000000000..2c03302f86ed --- /dev/null +++ b/Documentation/devicetree/bindings/clock/renesas,r8a7740-cpg-clocks.txt @@ -0,0 +1,41 @@ +These bindings should be considered EXPERIMENTAL for now. + +* Renesas R8A7740 Clock Pulse Generator (CPG) + +The CPG generates core clocks for the R8A7740 SoC. It includes three PLLs +and several fixed ratio and variable ratio dividers. + +Required Properties: + + - compatible: Must be "renesas,r8a7740-cpg-clocks" + + - reg: Base address and length of the memory resource used by the CPG + + - clocks: Reference to the three parent clocks + - #clock-cells: Must be 1 + - clock-output-names: The names of the clocks. Supported clocks are + "system", "pllc0", "pllc1", "pllc2", "r", "usb24s", "i", "zg", "b", + "m1", "hp", "hpp", "usbp", "s", "zb", "m3", and "cp". + + - renesas,mode: board-specific settings of the MD_CK* bits + + +Example +------- + +cpg_clocks: cpg_clocks@e6150000 { + compatible = "renesas,r8a7740-cpg-clocks"; + reg = <0xe6150000 0x10000>; + clocks = <&extal1_clk>, <&extal2_clk>, <&extalr_clk>; + #clock-cells = <1>; + clock-output-names = "system", "pllc0", "pllc1", + "pllc2", "r", + "usb24s", + "i", "zg", "b", "m1", "hp", + "hpp", "usbp", "s", "zb", "m3", + "cp"; +}; + +&cpg_clocks { + renesas,mode = <0x05>; +}; diff --git a/drivers/clk/shmobile/Makefile b/drivers/clk/shmobile/Makefile index bdf342daefa5..e0029237827a 100644 --- a/drivers/clk/shmobile/Makefile +++ b/drivers/clk/shmobile/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_ARCH_EMEV2) += clk-emev2.o obj-$(CONFIG_ARCH_R7S72100) += clk-rz.o +obj-$(CONFIG_ARCH_R8A7740) += clk-r8a7740.o obj-$(CONFIG_ARCH_R8A7779) += clk-r8a7779.o obj-$(CONFIG_ARCH_R8A7790) += clk-rcar-gen2.o obj-$(CONFIG_ARCH_R8A7791) += clk-rcar-gen2.o diff --git a/drivers/clk/shmobile/clk-r8a7740.c b/drivers/clk/shmobile/clk-r8a7740.c new file mode 100644 index 000000000000..1e2eaae21e01 --- /dev/null +++ b/drivers/clk/shmobile/clk-r8a7740.c @@ -0,0 +1,199 @@ +/* + * r8a7740 Core CPG Clocks + * + * Copyright (C) 2014 Ulrich Hecht + * + * 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 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct r8a7740_cpg { + struct clk_onecell_data data; + spinlock_t lock; + void __iomem *reg; +}; + +#define CPG_FRQCRA 0x00 +#define CPG_FRQCRB 0x04 +#define CPG_PLLC2CR 0x2c +#define CPG_USBCKCR 0x8c +#define CPG_FRQCRC 0xe0 + +#define CLK_ENABLE_ON_INIT BIT(0) + +struct div4_clk { + const char *name; + unsigned int reg; + unsigned int shift; + int flags; +}; + +static struct div4_clk div4_clks[] = { + { "i", CPG_FRQCRA, 20, CLK_ENABLE_ON_INIT }, + { "zg", CPG_FRQCRA, 16, CLK_ENABLE_ON_INIT }, + { "b", CPG_FRQCRA, 8, CLK_ENABLE_ON_INIT }, + { "m1", CPG_FRQCRA, 4, CLK_ENABLE_ON_INIT }, + { "hp", CPG_FRQCRB, 4, 0 }, + { "hpp", CPG_FRQCRC, 20, 0 }, + { "usbp", CPG_FRQCRC, 16, 0 }, + { "s", CPG_FRQCRC, 12, 0 }, + { "zb", CPG_FRQCRC, 8, 0 }, + { "m3", CPG_FRQCRC, 4, 0 }, + { "cp", CPG_FRQCRC, 0, 0 }, + { NULL, 0, 0, 0 }, +}; + +static const struct clk_div_table div4_div_table[] = { + { 0, 2 }, { 1, 3 }, { 2, 4 }, { 3, 6 }, { 4, 8 }, { 5, 12 }, + { 6, 16 }, { 7, 18 }, { 8, 24 }, { 9, 32 }, { 10, 36 }, { 11, 48 }, + { 13, 72 }, { 14, 96 }, { 0, 0 } +}; + +static u32 cpg_mode __initdata; + +static struct clk * __init +r8a7740_cpg_register_clock(struct device_node *np, struct r8a7740_cpg *cpg, + const char *name) +{ + const struct clk_div_table *table = NULL; + const char *parent_name; + unsigned int shift, reg; + unsigned int mult = 1; + unsigned int div = 1; + + if (!strcmp(name, "r")) { + switch (cpg_mode & (BIT(2) | BIT(1))) { + case BIT(1) | BIT(2): + /* extal1 */ + parent_name = of_clk_get_parent_name(np, 0); + div = 2048; + break; + case BIT(2): + /* extal1 */ + parent_name = of_clk_get_parent_name(np, 0); + div = 1024; + break; + default: + /* extalr */ + parent_name = of_clk_get_parent_name(np, 2); + break; + } + } else if (!strcmp(name, "system")) { + parent_name = of_clk_get_parent_name(np, 0); + if (cpg_mode & BIT(1)) + div = 2; + } else if (!strcmp(name, "pllc0")) { + /* PLLC0/1 are configurable multiplier clocks. Register them as + * fixed factor clocks for now as there's no generic multiplier + * clock implementation and we currently have no need to change + * the multiplier value. + */ + u32 value = clk_readl(cpg->reg + CPG_FRQCRC); + parent_name = "system"; + mult = ((value >> 24) & 0x7f) + 1; + } else if (!strcmp(name, "pllc1")) { + u32 value = clk_readl(cpg->reg + CPG_FRQCRA); + parent_name = "system"; + mult = ((value >> 24) & 0x7f) + 1; + div = 2; + } else if (!strcmp(name, "pllc2")) { + u32 value = clk_readl(cpg->reg + CPG_PLLC2CR); + parent_name = "system"; + mult = ((value >> 24) & 0x3f) + 1; + } else if (!strcmp(name, "usb24s")) { + u32 value = clk_readl(cpg->reg + CPG_USBCKCR); + if (value & BIT(7)) + /* extal2 */ + parent_name = of_clk_get_parent_name(np, 1); + else + parent_name = "system"; + if (!(value & BIT(6))) + div = 2; + } else { + struct div4_clk *c; + for (c = div4_clks; c->name; c++) { + if (!strcmp(name, c->name)) { + parent_name = "pllc1"; + table = div4_div_table; + reg = c->reg; + shift = c->shift; + break; + } + } + if (!c->name) + return ERR_PTR(-EINVAL); + } + + if (!table) { + return clk_register_fixed_factor(NULL, name, parent_name, 0, + mult, div); + } else { + return clk_register_divider_table(NULL, name, parent_name, 0, + cpg->reg + reg, shift, 4, 0, + table, &cpg->lock); + } +} + +static void __init r8a7740_cpg_clocks_init(struct device_node *np) +{ + struct r8a7740_cpg *cpg; + struct clk **clks; + unsigned int i; + int num_clks; + + if (of_property_read_u32(np, "renesas,mode", &cpg_mode)) + pr_warn("%s: missing renesas,mode property\n", __func__); + + num_clks = of_property_count_strings(np, "clock-output-names"); + if (num_clks < 0) { + pr_err("%s: failed to count clocks\n", __func__); + return; + } + + cpg = kzalloc(sizeof(*cpg), GFP_KERNEL); + clks = kzalloc(num_clks * sizeof(*clks), GFP_KERNEL); + if (cpg == NULL || clks == NULL) { + /* We're leaking memory on purpose, there's no point in cleaning + * up as the system won't boot anyway. + */ + return; + } + + spin_lock_init(&cpg->lock); + + cpg->data.clks = clks; + cpg->data.clk_num = num_clks; + + cpg->reg = of_iomap(np, 0); + if (WARN_ON(cpg->reg == NULL)) + return; + + for (i = 0; i < num_clks; ++i) { + const char *name; + struct clk *clk; + + of_property_read_string_index(np, "clock-output-names", i, + &name); + + clk = r8a7740_cpg_register_clock(np, cpg, name); + if (IS_ERR(clk)) + pr_err("%s: failed to register %s %s clock (%ld)\n", + __func__, np->name, name, PTR_ERR(clk)); + else + cpg->data.clks[i] = clk; + } + + of_clk_add_provider(np, of_clk_src_onecell_get, &cpg->data); +} +CLK_OF_DECLARE(r8a7740_cpg_clks, "renesas,r8a7740-cpg-clocks", + r8a7740_cpg_clocks_init); -- cgit v1.2.3 From c685841ee127e8023a33be68fd8af6fe192a3665 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Fri, 16 May 2014 16:07:13 -0700 Subject: clk: qcom: Support msm8974pro global clock control hardware A new PLL (gpll4) is added on msm8974 PRO devices to support a faster sdc1 clock rate. Add support for this and the two new sdcc cal clocks. Signed-off-by: Stephen Boyd Signed-off-by: Mike Turquette --- .../devicetree/bindings/clock/qcom,gcc.txt | 2 + drivers/clk/qcom/gcc-msm8974.c | 130 ++++++++++++++++++++- include/dt-bindings/clock/qcom,gcc-msm8974.h | 4 + 3 files changed, 130 insertions(+), 6 deletions(-) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/clock/qcom,gcc.txt b/Documentation/devicetree/bindings/clock/qcom,gcc.txt index 7b7104e8cb1e..9cfcb4f2bc97 100644 --- a/Documentation/devicetree/bindings/clock/qcom,gcc.txt +++ b/Documentation/devicetree/bindings/clock/qcom,gcc.txt @@ -8,6 +8,8 @@ Required properties : "qcom,gcc-msm8660" "qcom,gcc-msm8960" "qcom,gcc-msm8974" + "qcom,gcc-msm8974pro" + "qcom,gcc-msm8974pro-ac" - reg : shall contain base register location and length - #clock-cells : shall contain 1 diff --git a/drivers/clk/qcom/gcc-msm8974.c b/drivers/clk/qcom/gcc-msm8974.c index 7a420fcdb89e..7af7c18d2144 100644 --- a/drivers/clk/qcom/gcc-msm8974.c +++ b/drivers/clk/qcom/gcc-msm8974.c @@ -35,6 +35,7 @@ #define P_XO 0 #define P_GPLL0 1 #define P_GPLL1 1 +#define P_GPLL4 2 static const u8 gcc_xo_gpll0_map[] = { [P_XO] = 0, @@ -46,6 +47,18 @@ static const char *gcc_xo_gpll0[] = { "gpll0_vote", }; +static const u8 gcc_xo_gpll0_gpll4_map[] = { + [P_XO] = 0, + [P_GPLL0] = 1, + [P_GPLL4] = 5, +}; + +static const char *gcc_xo_gpll0_gpll4[] = { + "xo", + "gpll0_vote", + "gpll4_vote", +}; + #define F(f, s, h, m, n) { (f), (s), (2 * (h) - 1), (m), (n) } static struct clk_pll gpll0 = { @@ -138,6 +151,33 @@ static struct clk_regmap gpll1_vote = { }, }; +static struct clk_pll gpll4 = { + .l_reg = 0x1dc4, + .m_reg = 0x1dc8, + .n_reg = 0x1dcc, + .config_reg = 0x1dd4, + .mode_reg = 0x1dc0, + .status_reg = 0x1ddc, + .status_bit = 17, + .clkr.hw.init = &(struct clk_init_data){ + .name = "gpll4", + .parent_names = (const char *[]){ "xo" }, + .num_parents = 1, + .ops = &clk_pll_ops, + }, +}; + +static struct clk_regmap gpll4_vote = { + .enable_reg = 0x1480, + .enable_mask = BIT(4), + .hw.init = &(struct clk_init_data){ + .name = "gpll4_vote", + .parent_names = (const char *[]){ "gpll4" }, + .num_parents = 1, + .ops = &clk_pll_vote_ops, + }, +}; + static const struct freq_tbl ftbl_gcc_usb30_master_clk[] = { F(125000000, P_GPLL0, 1, 5, 24), { } @@ -812,18 +852,33 @@ static const struct freq_tbl ftbl_gcc_sdcc1_4_apps_clk[] = { { } }; +static const struct freq_tbl ftbl_gcc_sdcc1_apps_clk_pro[] = { + F(144000, P_XO, 16, 3, 25), + F(400000, P_XO, 12, 1, 4), + F(20000000, P_GPLL0, 15, 1, 2), + F(25000000, P_GPLL0, 12, 1, 2), + F(50000000, P_GPLL0, 12, 0, 0), + F(100000000, P_GPLL0, 6, 0, 0), + F(192000000, P_GPLL4, 4, 0, 0), + F(200000000, P_GPLL0, 3, 0, 0), + F(384000000, P_GPLL4, 2, 0, 0), + { } +}; + +static struct clk_init_data sdcc1_apps_clk_src_init = { + .name = "sdcc1_apps_clk_src", + .parent_names = gcc_xo_gpll0, + .num_parents = 2, + .ops = &clk_rcg2_ops, +}; + static struct clk_rcg2 sdcc1_apps_clk_src = { .cmd_rcgr = 0x04d0, .mnd_width = 8, .hid_width = 5, .parent_map = gcc_xo_gpll0_map, .freq_tbl = ftbl_gcc_sdcc1_4_apps_clk, - .clkr.hw.init = &(struct clk_init_data){ - .name = "sdcc1_apps_clk_src", - .parent_names = gcc_xo_gpll0, - .num_parents = 2, - .ops = &clk_rcg2_ops, - }, + .clkr.hw.init = &sdcc1_apps_clk_src_init, }; static struct clk_rcg2 sdcc2_apps_clk_src = { @@ -1995,6 +2050,38 @@ static struct clk_branch gcc_sdcc1_apps_clk = { }, }; +static struct clk_branch gcc_sdcc1_cdccal_ff_clk = { + .halt_reg = 0x04e8, + .clkr = { + .enable_reg = 0x04e8, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_sdcc1_cdccal_ff_clk", + .parent_names = (const char *[]){ + "xo" + }, + .num_parents = 1, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc1_cdccal_sleep_clk = { + .halt_reg = 0x04e4, + .clkr = { + .enable_reg = 0x04e4, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_sdcc1_cdccal_sleep_clk", + .parent_names = (const char *[]){ + "sleep_clk_src" + }, + .num_parents = 1, + .ops = &clk_branch2_ops, + }, + }, +}; + static struct clk_branch gcc_sdcc2_ahb_clk = { .halt_reg = 0x0508, .clkr = { @@ -2484,6 +2571,10 @@ static struct clk_regmap *gcc_msm8974_clocks[] = { [GCC_USB_HSIC_IO_CAL_SLEEP_CLK] = &gcc_usb_hsic_io_cal_sleep_clk.clkr, [GCC_USB_HSIC_SYSTEM_CLK] = &gcc_usb_hsic_system_clk.clkr, [GCC_MMSS_GPLL0_CLK_SRC] = &gcc_mmss_gpll0_clk_src, + [GPLL4] = NULL, + [GPLL4_VOTE] = NULL, + [GCC_SDCC1_CDCCAL_SLEEP_CLK] = NULL, + [GCC_SDCC1_CDCCAL_FF_CLK] = NULL, }; static const struct qcom_reset_map gcc_msm8974_resets[] = { @@ -2585,14 +2676,41 @@ static const struct qcom_cc_desc gcc_msm8974_desc = { static const struct of_device_id gcc_msm8974_match_table[] = { { .compatible = "qcom,gcc-msm8974" }, + { .compatible = "qcom,gcc-msm8974pro" , .data = (void *)1UL }, + { .compatible = "qcom,gcc-msm8974pro-ac", .data = (void *)1UL }, { } }; MODULE_DEVICE_TABLE(of, gcc_msm8974_match_table); +static void msm8974_pro_clock_override(void) +{ + sdcc1_apps_clk_src_init.parent_names = gcc_xo_gpll0_gpll4; + sdcc1_apps_clk_src_init.num_parents = 3; + sdcc1_apps_clk_src.freq_tbl = ftbl_gcc_sdcc1_apps_clk_pro; + sdcc1_apps_clk_src.parent_map = gcc_xo_gpll0_gpll4_map; + + gcc_msm8974_clocks[GPLL4] = &gpll4.clkr; + gcc_msm8974_clocks[GPLL4_VOTE] = &gpll4_vote; + gcc_msm8974_clocks[GCC_SDCC1_CDCCAL_SLEEP_CLK] = + &gcc_sdcc1_cdccal_sleep_clk.clkr; + gcc_msm8974_clocks[GCC_SDCC1_CDCCAL_FF_CLK] = + &gcc_sdcc1_cdccal_ff_clk.clkr; +} + static int gcc_msm8974_probe(struct platform_device *pdev) { struct clk *clk; struct device *dev = &pdev->dev; + bool pro; + const struct of_device_id *id; + + id = of_match_device(gcc_msm8974_match_table, dev); + if (!id) + return -ENODEV; + pro = !!(id->data); + + if (pro) + msm8974_pro_clock_override(); /* Temporary until RPM clocks supported */ clk = clk_register_fixed_rate(dev, "xo", NULL, CLK_IS_ROOT, 19200000); diff --git a/include/dt-bindings/clock/qcom,gcc-msm8974.h b/include/dt-bindings/clock/qcom,gcc-msm8974.h index 223ca174d9d3..51e51c860fe6 100644 --- a/include/dt-bindings/clock/qcom,gcc-msm8974.h +++ b/include/dt-bindings/clock/qcom,gcc-msm8974.h @@ -316,5 +316,9 @@ #define GCC_CE2_CLK_SLEEP_ENA 299 #define GCC_CE2_AXI_CLK_SLEEP_ENA 300 #define GCC_CE2_AHB_CLK_SLEEP_ENA 301 +#define GPLL4 302 +#define GPLL4_VOTE 303 +#define GCC_SDCC1_CDCCAL_SLEEP_CLK 304 +#define GCC_SDCC1_CDCCAL_FF_CLK 305 #endif -- cgit v1.2.3 From 3cbcb16095f916f50a5a55066fcc4be06946ce1e Mon Sep 17 00:00:00 2001 From: David Lanzendörfer Date: Mon, 12 May 2014 14:04:48 +0200 Subject: mmc: sunxi: Add driver for SD/MMC hosts found on Allwinner sunxi SoCs The Allwinner sunxi mmc host uses dma in bus-master mode using a built-in designware idmac controller, which is identical to the one found in the mmc-dw hosts. However the rest of the host is not identical to mmc-dw, it deals with sending stop commands in hardware which makes it significantly different from the mmc-dw devices. Signed-off-by: David Lanzendörfer [hdegoede@redhat.com: various cleanups and fixes] Signed-off-by: Hans de Goede Acked-by: Maxime Ripard Acked-by: Arnd Bergmann Signed-off-by: Ulf Hansson Acked-by: Chris Ball Signed-off-by: Mike Turquette --- .../devicetree/bindings/mmc/sunxi-mmc.txt | 43 + drivers/mmc/host/Kconfig | 7 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/sunxi-mmc.c | 1049 ++++++++++++++++++++ 4 files changed, 1100 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/sunxi-mmc.txt create mode 100644 drivers/mmc/host/sunxi-mmc.c (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/mmc/sunxi-mmc.txt b/Documentation/devicetree/bindings/mmc/sunxi-mmc.txt new file mode 100644 index 000000000000..91b3a3467150 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/sunxi-mmc.txt @@ -0,0 +1,43 @@ +* Allwinner sunxi MMC controller + +The highspeed MMC host controller on Allwinner SoCs provides an interface +for MMC, SD and SDIO types of memory cards. + +Supported maximum speeds are the ones of the eMMC standard 4.5 as well +as the speed of SD standard 3.0. +Absolute maximum transfer rate is 200MB/s + +Required properties: + - compatible : "allwinner,sun4i-a10-mmc" or "allwinner,sun5i-a13-mmc" + - reg : mmc controller base registers + - clocks : a list with 2 phandle + clock specifier pairs + - clock-names : must contain "ahb" and "mmc" + - interrupts : mmc controller interrupt + +Optional properties: + - resets : phandle + reset specifier pair + - reset-names : must contain "ahb" + - for cd, bus-width and additional generic mmc parameters + please refer to mmc.txt within this directory + +Examples: + - Within .dtsi: + mmc0: mmc@01c0f000 { + compatible = "allwinner,sun5i-a13-mmc"; + reg = <0x01c0f000 0x1000>; + clocks = <&ahb_gates 8>, <&mmc0_clk>; + clock-names = "ahb", "mod"; + interrupts = <0 32 4>; + status = "disabled"; + }; + + - Within dts: + mmc0: mmc@01c0f000 { + pinctrl-names = "default", "default"; + pinctrl-0 = <&mmc0_pins_a>; + pinctrl-1 = <&mmc0_cd_pin_reference_design>; + bus-width = <4>; + cd-gpios = <&pio 7 1 0>; /* PH1 */ + cd-inverted; + status = "okay"; + }; diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 8aaf8c1f3f63..d50ac1ca11ea 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -694,3 +694,10 @@ config MMC_REALTEK_PCI help Say Y here to include driver code to support SD/MMC card interface of Realtek PCI-E card reader + +config MMC_SUNXI + tristate "Allwinner sunxi SD/MMC Host Controller support" + depends on ARCH_SUNXI + help + This selects support for the SD/MMC Host Controller on + Allwinner sunxi SoCs. diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 0c8aa5e1e304..3072bfb6d26a 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o obj-$(CONFIG_MMC_VUB300) += vub300.o obj-$(CONFIG_MMC_USHC) += ushc.o obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o +obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c new file mode 100644 index 000000000000..024f67c98cdc --- /dev/null +++ b/drivers/mmc/host/sunxi-mmc.c @@ -0,0 +1,1049 @@ +/* + * Driver for sunxi SD/MMC host controllers + * (C) Copyright 2007-2011 Reuuimlla Technology Co., Ltd. + * (C) Copyright 2007-2011 Aaron Maoye + * (C) Copyright 2013-2014 O2S GmbH + * (C) Copyright 2013-2014 David Lanzend�rfer + * (C) Copyright 2013-2014 Hans de Goede + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* register offset definitions */ +#define SDXC_REG_GCTRL (0x00) /* SMC Global Control Register */ +#define SDXC_REG_CLKCR (0x04) /* SMC Clock Control Register */ +#define SDXC_REG_TMOUT (0x08) /* SMC Time Out Register */ +#define SDXC_REG_WIDTH (0x0C) /* SMC Bus Width Register */ +#define SDXC_REG_BLKSZ (0x10) /* SMC Block Size Register */ +#define SDXC_REG_BCNTR (0x14) /* SMC Byte Count Register */ +#define SDXC_REG_CMDR (0x18) /* SMC Command Register */ +#define SDXC_REG_CARG (0x1C) /* SMC Argument Register */ +#define SDXC_REG_RESP0 (0x20) /* SMC Response Register 0 */ +#define SDXC_REG_RESP1 (0x24) /* SMC Response Register 1 */ +#define SDXC_REG_RESP2 (0x28) /* SMC Response Register 2 */ +#define SDXC_REG_RESP3 (0x2C) /* SMC Response Register 3 */ +#define SDXC_REG_IMASK (0x30) /* SMC Interrupt Mask Register */ +#define SDXC_REG_MISTA (0x34) /* SMC Masked Interrupt Status Register */ +#define SDXC_REG_RINTR (0x38) /* SMC Raw Interrupt Status Register */ +#define SDXC_REG_STAS (0x3C) /* SMC Status Register */ +#define SDXC_REG_FTRGL (0x40) /* SMC FIFO Threshold Watermark Registe */ +#define SDXC_REG_FUNS (0x44) /* SMC Function Select Register */ +#define SDXC_REG_CBCR (0x48) /* SMC CIU Byte Count Register */ +#define SDXC_REG_BBCR (0x4C) /* SMC BIU Byte Count Register */ +#define SDXC_REG_DBGC (0x50) /* SMC Debug Enable Register */ +#define SDXC_REG_HWRST (0x78) /* SMC Card Hardware Reset for Register */ +#define SDXC_REG_DMAC (0x80) /* SMC IDMAC Control Register */ +#define SDXC_REG_DLBA (0x84) /* SMC IDMAC Descriptor List Base Addre */ +#define SDXC_REG_IDST (0x88) /* SMC IDMAC Status Register */ +#define SDXC_REG_IDIE (0x8C) /* SMC IDMAC Interrupt Enable Register */ +#define SDXC_REG_CHDA (0x90) +#define SDXC_REG_CBDA (0x94) + +#define mmc_readl(host, reg) \ + readl((host)->reg_base + SDXC_##reg) +#define mmc_writel(host, reg, value) \ + writel((value), (host)->reg_base + SDXC_##reg) + +/* global control register bits */ +#define SDXC_SOFT_RESET BIT(0) +#define SDXC_FIFO_RESET BIT(1) +#define SDXC_DMA_RESET BIT(2) +#define SDXC_INTERRUPT_ENABLE_BIT BIT(4) +#define SDXC_DMA_ENABLE_BIT BIT(5) +#define SDXC_DEBOUNCE_ENABLE_BIT BIT(8) +#define SDXC_POSEDGE_LATCH_DATA BIT(9) +#define SDXC_DDR_MODE BIT(10) +#define SDXC_MEMORY_ACCESS_DONE BIT(29) +#define SDXC_ACCESS_DONE_DIRECT BIT(30) +#define SDXC_ACCESS_BY_AHB BIT(31) +#define SDXC_ACCESS_BY_DMA (0 << 31) +#define SDXC_HARDWARE_RESET \ + (SDXC_SOFT_RESET | SDXC_FIFO_RESET | SDXC_DMA_RESET) + +/* clock control bits */ +#define SDXC_CARD_CLOCK_ON BIT(16) +#define SDXC_LOW_POWER_ON BIT(17) + +/* bus width */ +#define SDXC_WIDTH1 0 +#define SDXC_WIDTH4 1 +#define SDXC_WIDTH8 2 + +/* smc command bits */ +#define SDXC_RESP_EXPIRE BIT(6) +#define SDXC_LONG_RESPONSE BIT(7) +#define SDXC_CHECK_RESPONSE_CRC BIT(8) +#define SDXC_DATA_EXPIRE BIT(9) +#define SDXC_WRITE BIT(10) +#define SDXC_SEQUENCE_MODE BIT(11) +#define SDXC_SEND_AUTO_STOP BIT(12) +#define SDXC_WAIT_PRE_OVER BIT(13) +#define SDXC_STOP_ABORT_CMD BIT(14) +#define SDXC_SEND_INIT_SEQUENCE BIT(15) +#define SDXC_UPCLK_ONLY BIT(21) +#define SDXC_READ_CEATA_DEV BIT(22) +#define SDXC_CCS_EXPIRE BIT(23) +#define SDXC_ENABLE_BIT_BOOT BIT(24) +#define SDXC_ALT_BOOT_OPTIONS BIT(25) +#define SDXC_BOOT_ACK_EXPIRE BIT(26) +#define SDXC_BOOT_ABORT BIT(27) +#define SDXC_VOLTAGE_SWITCH BIT(28) +#define SDXC_USE_HOLD_REGISTER BIT(29) +#define SDXC_START BIT(31) + +/* interrupt bits */ +#define SDXC_RESP_ERROR BIT(1) +#define SDXC_COMMAND_DONE BIT(2) +#define SDXC_DATA_OVER BIT(3) +#define SDXC_TX_DATA_REQUEST BIT(4) +#define SDXC_RX_DATA_REQUEST BIT(5) +#define SDXC_RESP_CRC_ERROR BIT(6) +#define SDXC_DATA_CRC_ERROR BIT(7) +#define SDXC_RESP_TIMEOUT BIT(8) +#define SDXC_DATA_TIMEOUT BIT(9) +#define SDXC_VOLTAGE_CHANGE_DONE BIT(10) +#define SDXC_FIFO_RUN_ERROR BIT(11) +#define SDXC_HARD_WARE_LOCKED BIT(12) +#define SDXC_START_BIT_ERROR BIT(13) +#define SDXC_AUTO_COMMAND_DONE BIT(14) +#define SDXC_END_BIT_ERROR BIT(15) +#define SDXC_SDIO_INTERRUPT BIT(16) +#define SDXC_CARD_INSERT BIT(30) +#define SDXC_CARD_REMOVE BIT(31) +#define SDXC_INTERRUPT_ERROR_BIT \ + (SDXC_RESP_ERROR | SDXC_RESP_CRC_ERROR | SDXC_DATA_CRC_ERROR | \ + SDXC_RESP_TIMEOUT | SDXC_DATA_TIMEOUT | SDXC_FIFO_RUN_ERROR | \ + SDXC_HARD_WARE_LOCKED | SDXC_START_BIT_ERROR | SDXC_END_BIT_ERROR) +#define SDXC_INTERRUPT_DONE_BIT \ + (SDXC_AUTO_COMMAND_DONE | SDXC_DATA_OVER | \ + SDXC_COMMAND_DONE | SDXC_VOLTAGE_CHANGE_DONE) + +/* status */ +#define SDXC_RXWL_FLAG BIT(0) +#define SDXC_TXWL_FLAG BIT(1) +#define SDXC_FIFO_EMPTY BIT(2) +#define SDXC_FIFO_FULL BIT(3) +#define SDXC_CARD_PRESENT BIT(8) +#define SDXC_CARD_DATA_BUSY BIT(9) +#define SDXC_DATA_FSM_BUSY BIT(10) +#define SDXC_DMA_REQUEST BIT(31) +#define SDXC_FIFO_SIZE 16 + +/* Function select */ +#define SDXC_CEATA_ON (0xceaa << 16) +#define SDXC_SEND_IRQ_RESPONSE BIT(0) +#define SDXC_SDIO_READ_WAIT BIT(1) +#define SDXC_ABORT_READ_DATA BIT(2) +#define SDXC_SEND_CCSD BIT(8) +#define SDXC_SEND_AUTO_STOPCCSD BIT(9) +#define SDXC_CEATA_DEV_IRQ_ENABLE BIT(10) + +/* IDMA controller bus mod bit field */ +#define SDXC_IDMAC_SOFT_RESET BIT(0) +#define SDXC_IDMAC_FIX_BURST BIT(1) +#define SDXC_IDMAC_IDMA_ON BIT(7) +#define SDXC_IDMAC_REFETCH_DES BIT(31) + +/* IDMA status bit field */ +#define SDXC_IDMAC_TRANSMIT_INTERRUPT BIT(0) +#define SDXC_IDMAC_RECEIVE_INTERRUPT BIT(1) +#define SDXC_IDMAC_FATAL_BUS_ERROR BIT(2) +#define SDXC_IDMAC_DESTINATION_INVALID BIT(4) +#define SDXC_IDMAC_CARD_ERROR_SUM BIT(5) +#define SDXC_IDMAC_NORMAL_INTERRUPT_SUM BIT(8) +#define SDXC_IDMAC_ABNORMAL_INTERRUPT_SUM BIT(9) +#define SDXC_IDMAC_HOST_ABORT_INTERRUPT BIT(10) +#define SDXC_IDMAC_IDLE (0 << 13) +#define SDXC_IDMAC_SUSPEND (1 << 13) +#define SDXC_IDMAC_DESC_READ (2 << 13) +#define SDXC_IDMAC_DESC_CHECK (3 << 13) +#define SDXC_IDMAC_READ_REQUEST_WAIT (4 << 13) +#define SDXC_IDMAC_WRITE_REQUEST_WAIT (5 << 13) +#define SDXC_IDMAC_READ (6 << 13) +#define SDXC_IDMAC_WRITE (7 << 13) +#define SDXC_IDMAC_DESC_CLOSE (8 << 13) + +/* +* If the idma-des-size-bits of property is ie 13, bufsize bits are: +* Bits 0-12: buf1 size +* Bits 13-25: buf2 size +* Bits 26-31: not used +* Since we only ever set buf1 size, we can simply store it directly. +*/ +#define SDXC_IDMAC_DES0_DIC BIT(1) /* disable interrupt on completion */ +#define SDXC_IDMAC_DES0_LD BIT(2) /* last descriptor */ +#define SDXC_IDMAC_DES0_FD BIT(3) /* first descriptor */ +#define SDXC_IDMAC_DES0_CH BIT(4) /* chain mode */ +#define SDXC_IDMAC_DES0_ER BIT(5) /* end of ring */ +#define SDXC_IDMAC_DES0_CES BIT(30) /* card error summary */ +#define SDXC_IDMAC_DES0_OWN BIT(31) /* 1-idma owns it, 0-host owns it */ + +struct sunxi_idma_des { + u32 config; + u32 buf_size; + u32 buf_addr_ptr1; + u32 buf_addr_ptr2; +}; + +struct sunxi_mmc_host { + struct mmc_host *mmc; + struct reset_control *reset; + + /* IO mapping base */ + void __iomem *reg_base; + + /* clock management */ + struct clk *clk_ahb; + struct clk *clk_mmc; + + /* irq */ + spinlock_t lock; + int irq; + u32 int_sum; + u32 sdio_imask; + + /* dma */ + u32 idma_des_size_bits; + dma_addr_t sg_dma; + void *sg_cpu; + bool wait_dma; + + struct mmc_request *mrq; + struct mmc_request *manual_stop_mrq; + int ferror; +}; + +static int sunxi_mmc_reset_host(struct sunxi_mmc_host *host) +{ + unsigned long expire = jiffies + msecs_to_jiffies(250); + u32 rval; + + mmc_writel(host, REG_CMDR, SDXC_HARDWARE_RESET); + do { + rval = mmc_readl(host, REG_GCTRL); + } while (time_before(jiffies, expire) && (rval & SDXC_HARDWARE_RESET)); + + if (rval & SDXC_HARDWARE_RESET) { + dev_err(mmc_dev(host->mmc), "fatal err reset timeout\n"); + return -EIO; + } + + return 0; +} + +static int sunxi_mmc_init_host(struct mmc_host *mmc) +{ + u32 rval; + struct sunxi_mmc_host *host = mmc_priv(mmc); + + if (sunxi_mmc_reset_host(host)) + return -EIO; + + mmc_writel(host, REG_FTRGL, 0x20070008); + mmc_writel(host, REG_TMOUT, 0xffffffff); + mmc_writel(host, REG_IMASK, host->sdio_imask); + mmc_writel(host, REG_RINTR, 0xffffffff); + mmc_writel(host, REG_DBGC, 0xdeb); + mmc_writel(host, REG_FUNS, SDXC_CEATA_ON); + mmc_writel(host, REG_DLBA, host->sg_dma); + + rval = mmc_readl(host, REG_GCTRL); + rval |= SDXC_INTERRUPT_ENABLE_BIT; + rval &= ~SDXC_ACCESS_DONE_DIRECT; + mmc_writel(host, REG_GCTRL, rval); + + return 0; +} + +static void sunxi_mmc_init_idma_des(struct sunxi_mmc_host *host, + struct mmc_data *data) +{ + struct sunxi_idma_des *pdes = (struct sunxi_idma_des *)host->sg_cpu; + struct sunxi_idma_des *pdes_pa = (struct sunxi_idma_des *)host->sg_dma; + int i, max_len = (1 << host->idma_des_size_bits); + + for (i = 0; i < data->sg_len; i++) { + pdes[i].config = SDXC_IDMAC_DES0_CH | SDXC_IDMAC_DES0_OWN | + SDXC_IDMAC_DES0_DIC; + + if (data->sg[i].length == max_len) + pdes[i].buf_size = 0; /* 0 == max_len */ + else + pdes[i].buf_size = data->sg[i].length; + + pdes[i].buf_addr_ptr1 = sg_dma_address(&data->sg[i]); + pdes[i].buf_addr_ptr2 = (u32)&pdes_pa[i + 1]; + } + + pdes[0].config |= SDXC_IDMAC_DES0_FD; + pdes[i - 1].config = SDXC_IDMAC_DES0_OWN | SDXC_IDMAC_DES0_LD; + + /* + * Avoid the io-store starting the idmac hitting io-mem before the + * descriptors hit the main-mem. + */ + wmb(); +} + +static enum dma_data_direction sunxi_mmc_get_dma_dir(struct mmc_data *data) +{ + if (data->flags & MMC_DATA_WRITE) + return DMA_TO_DEVICE; + else + return DMA_FROM_DEVICE; +} + +static int sunxi_mmc_map_dma(struct sunxi_mmc_host *host, + struct mmc_data *data) +{ + u32 i, dma_len; + struct scatterlist *sg; + + dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + sunxi_mmc_get_dma_dir(data)); + if (dma_len == 0) { + dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n"); + return -ENOMEM; + } + + for_each_sg(data->sg, sg, data->sg_len, i) { + if (sg->offset & 3 || sg->length & 3) { + dev_err(mmc_dev(host->mmc), + "unaligned scatterlist: os %x length %d\n", + sg->offset, sg->length); + return -EINVAL; + } + } + + return 0; +} + +static void sunxi_mmc_start_dma(struct sunxi_mmc_host *host, + struct mmc_data *data) +{ + u32 rval; + + sunxi_mmc_init_idma_des(host, data); + + rval = mmc_readl(host, REG_GCTRL); + rval |= SDXC_DMA_ENABLE_BIT; + mmc_writel(host, REG_GCTRL, rval); + rval |= SDXC_DMA_RESET; + mmc_writel(host, REG_GCTRL, rval); + + mmc_writel(host, REG_DMAC, SDXC_IDMAC_SOFT_RESET); + + if (!(data->flags & MMC_DATA_WRITE)) + mmc_writel(host, REG_IDIE, SDXC_IDMAC_RECEIVE_INTERRUPT); + + mmc_writel(host, REG_DMAC, + SDXC_IDMAC_FIX_BURST | SDXC_IDMAC_IDMA_ON); +} + +static void sunxi_mmc_send_manual_stop(struct sunxi_mmc_host *host, + struct mmc_request *req) +{ + u32 arg, cmd_val, ri; + unsigned long expire = jiffies + msecs_to_jiffies(1000); + + cmd_val = SDXC_START | SDXC_RESP_EXPIRE | + SDXC_STOP_ABORT_CMD | SDXC_CHECK_RESPONSE_CRC; + + if (req->cmd->opcode == SD_IO_RW_EXTENDED) { + cmd_val |= SD_IO_RW_DIRECT; + arg = (1 << 31) | (0 << 28) | (SDIO_CCCR_ABORT << 9) | + ((req->cmd->arg >> 28) & 0x7); + } else { + cmd_val |= MMC_STOP_TRANSMISSION; + arg = 0; + } + + mmc_writel(host, REG_CARG, arg); + mmc_writel(host, REG_CMDR, cmd_val); + + do { + ri = mmc_readl(host, REG_RINTR); + } while (!(ri & (SDXC_COMMAND_DONE | SDXC_INTERRUPT_ERROR_BIT)) && + time_before(jiffies, expire)); + + if (!(ri & SDXC_COMMAND_DONE) || (ri & SDXC_INTERRUPT_ERROR_BIT)) { + dev_err(mmc_dev(host->mmc), "send stop command failed\n"); + if (req->stop) + req->stop->resp[0] = -ETIMEDOUT; + } else { + if (req->stop) + req->stop->resp[0] = mmc_readl(host, REG_RESP0); + } + + mmc_writel(host, REG_RINTR, 0xffff); +} + +static void sunxi_mmc_dump_errinfo(struct sunxi_mmc_host *host) +{ + struct mmc_command *cmd = host->mrq->cmd; + struct mmc_data *data = host->mrq->data; + + /* For some cmds timeout is normal with sd/mmc cards */ + if ((host->int_sum & SDXC_INTERRUPT_ERROR_BIT) == + SDXC_RESP_TIMEOUT && (cmd->opcode == SD_IO_SEND_OP_COND || + cmd->opcode == SD_IO_RW_DIRECT)) + return; + + dev_err(mmc_dev(host->mmc), + "smc %d err, cmd %d,%s%s%s%s%s%s%s%s%s%s !!\n", + host->mmc->index, cmd->opcode, + data ? (data->flags & MMC_DATA_WRITE ? " WR" : " RD") : "", + host->int_sum & SDXC_RESP_ERROR ? " RE" : "", + host->int_sum & SDXC_RESP_CRC_ERROR ? " RCE" : "", + host->int_sum & SDXC_DATA_CRC_ERROR ? " DCE" : "", + host->int_sum & SDXC_RESP_TIMEOUT ? " RTO" : "", + host->int_sum & SDXC_DATA_TIMEOUT ? " DTO" : "", + host->int_sum & SDXC_FIFO_RUN_ERROR ? " FE" : "", + host->int_sum & SDXC_HARD_WARE_LOCKED ? " HL" : "", + host->int_sum & SDXC_START_BIT_ERROR ? " SBE" : "", + host->int_sum & SDXC_END_BIT_ERROR ? " EBE" : "" + ); +} + +/* Called in interrupt context! */ +static irqreturn_t sunxi_mmc_finalize_request(struct sunxi_mmc_host *host) +{ + struct mmc_request *mrq = host->mrq; + struct mmc_data *data = mrq->data; + u32 rval; + + mmc_writel(host, REG_IMASK, host->sdio_imask); + mmc_writel(host, REG_IDIE, 0); + + if (host->int_sum & SDXC_INTERRUPT_ERROR_BIT) { + sunxi_mmc_dump_errinfo(host); + mrq->cmd->error = -ETIMEDOUT; + + if (data) { + data->error = -ETIMEDOUT; + host->manual_stop_mrq = mrq; + } + + if (mrq->stop) + mrq->stop->error = -ETIMEDOUT; + } else { + if (mrq->cmd->flags & MMC_RSP_136) { + mrq->cmd->resp[0] = mmc_readl(host, REG_RESP3); + mrq->cmd->resp[1] = mmc_readl(host, REG_RESP2); + mrq->cmd->resp[2] = mmc_readl(host, REG_RESP1); + mrq->cmd->resp[3] = mmc_readl(host, REG_RESP0); + } else { + mrq->cmd->resp[0] = mmc_readl(host, REG_RESP0); + } + + if (data) + data->bytes_xfered = data->blocks * data->blksz; + } + + if (data) { + mmc_writel(host, REG_IDST, 0x337); + mmc_writel(host, REG_DMAC, 0); + rval = mmc_readl(host, REG_GCTRL); + rval |= SDXC_DMA_RESET; + mmc_writel(host, REG_GCTRL, rval); + rval &= ~SDXC_DMA_ENABLE_BIT; + mmc_writel(host, REG_GCTRL, rval); + rval |= SDXC_FIFO_RESET; + mmc_writel(host, REG_GCTRL, rval); + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + sunxi_mmc_get_dma_dir(data)); + } + + mmc_writel(host, REG_RINTR, 0xffff); + + host->mrq = NULL; + host->int_sum = 0; + host->wait_dma = false; + + return host->manual_stop_mrq ? IRQ_WAKE_THREAD : IRQ_HANDLED; +} + +static irqreturn_t sunxi_mmc_irq(int irq, void *dev_id) +{ + struct sunxi_mmc_host *host = dev_id; + struct mmc_request *mrq; + u32 msk_int, idma_int; + bool finalize = false; + bool sdio_int = false; + irqreturn_t ret = IRQ_HANDLED; + + spin_lock(&host->lock); + + idma_int = mmc_readl(host, REG_IDST); + msk_int = mmc_readl(host, REG_MISTA); + + dev_dbg(mmc_dev(host->mmc), "irq: rq %p mi %08x idi %08x\n", + host->mrq, msk_int, idma_int); + + mrq = host->mrq; + if (mrq) { + if (idma_int & SDXC_IDMAC_RECEIVE_INTERRUPT) + host->wait_dma = false; + + host->int_sum |= msk_int; + + /* Wait for COMMAND_DONE on RESPONSE_TIMEOUT before finalize */ + if ((host->int_sum & SDXC_RESP_TIMEOUT) && + !(host->int_sum & SDXC_COMMAND_DONE)) + mmc_writel(host, REG_IMASK, + host->sdio_imask | SDXC_COMMAND_DONE); + /* Don't wait for dma on error */ + else if (host->int_sum & SDXC_INTERRUPT_ERROR_BIT) + finalize = true; + else if ((host->int_sum & SDXC_INTERRUPT_DONE_BIT) && + !host->wait_dma) + finalize = true; + } + + if (msk_int & SDXC_SDIO_INTERRUPT) + sdio_int = true; + + mmc_writel(host, REG_RINTR, msk_int); + mmc_writel(host, REG_IDST, idma_int); + + if (finalize) + ret = sunxi_mmc_finalize_request(host); + + spin_unlock(&host->lock); + + if (finalize && ret == IRQ_HANDLED) + mmc_request_done(host->mmc, mrq); + + if (sdio_int) + mmc_signal_sdio_irq(host->mmc); + + return ret; +} + +static irqreturn_t sunxi_mmc_handle_manual_stop(int irq, void *dev_id) +{ + struct sunxi_mmc_host *host = dev_id; + struct mmc_request *mrq; + unsigned long iflags; + + spin_lock_irqsave(&host->lock, iflags); + mrq = host->manual_stop_mrq; + spin_unlock_irqrestore(&host->lock, iflags); + + if (!mrq) { + dev_err(mmc_dev(host->mmc), "no request for manual stop\n"); + return IRQ_HANDLED; + } + + dev_err(mmc_dev(host->mmc), "data error, sending stop command\n"); + sunxi_mmc_send_manual_stop(host, mrq); + + spin_lock_irqsave(&host->lock, iflags); + host->manual_stop_mrq = NULL; + spin_unlock_irqrestore(&host->lock, iflags); + + mmc_request_done(host->mmc, mrq); + + return IRQ_HANDLED; +} + +static int sunxi_mmc_oclk_onoff(struct sunxi_mmc_host *host, u32 oclk_en) +{ + unsigned long expire = jiffies + msecs_to_jiffies(250); + u32 rval; + + rval = mmc_readl(host, REG_CLKCR); + rval &= ~(SDXC_CARD_CLOCK_ON | SDXC_LOW_POWER_ON); + + if (oclk_en) + rval |= SDXC_CARD_CLOCK_ON; + + mmc_writel(host, REG_CLKCR, rval); + + rval = SDXC_START | SDXC_UPCLK_ONLY | SDXC_WAIT_PRE_OVER; + mmc_writel(host, REG_CMDR, rval); + + do { + rval = mmc_readl(host, REG_CMDR); + } while (time_before(jiffies, expire) && (rval & SDXC_START)); + + /* clear irq status bits set by the command */ + mmc_writel(host, REG_RINTR, + mmc_readl(host, REG_RINTR) & ~SDXC_SDIO_INTERRUPT); + + if (rval & SDXC_START) { + dev_err(mmc_dev(host->mmc), "fatal err update clk timeout\n"); + return -EIO; + } + + return 0; +} + +static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host, + struct mmc_ios *ios) +{ + u32 rate, oclk_dly, rval, sclk_dly, src_clk; + int ret; + + rate = clk_round_rate(host->clk_mmc, ios->clock); + dev_dbg(mmc_dev(host->mmc), "setting clk to %d, rounded %d\n", + ios->clock, rate); + + /* setting clock rate */ + ret = clk_set_rate(host->clk_mmc, rate); + if (ret) { + dev_err(mmc_dev(host->mmc), "error setting clk to %d: %d\n", + rate, ret); + return ret; + } + + ret = sunxi_mmc_oclk_onoff(host, 0); + if (ret) + return ret; + + /* clear internal divider */ + rval = mmc_readl(host, REG_CLKCR); + rval &= ~0xff; + mmc_writel(host, REG_CLKCR, rval); + + /* determine delays */ + if (rate <= 400000) { + oclk_dly = 0; + sclk_dly = 7; + } else if (rate <= 25000000) { + oclk_dly = 0; + sclk_dly = 5; + } else if (rate <= 50000000) { + if (ios->timing == MMC_TIMING_UHS_DDR50) { + oclk_dly = 2; + sclk_dly = 4; + } else { + oclk_dly = 3; + sclk_dly = 5; + } + } else { + /* rate > 50000000 */ + oclk_dly = 2; + sclk_dly = 4; + } + + src_clk = clk_get_rate(clk_get_parent(host->clk_mmc)); + if (src_clk >= 300000000 && src_clk <= 400000000) { + if (oclk_dly) + oclk_dly--; + if (sclk_dly) + sclk_dly--; + } + + clk_sunxi_mmc_phase_control(host->clk_mmc, sclk_dly, oclk_dly); + + return sunxi_mmc_oclk_onoff(host, 1); +} + +static void sunxi_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct sunxi_mmc_host *host = mmc_priv(mmc); + u32 rval; + + /* Set the power state */ + switch (ios->power_mode) { + case MMC_POWER_ON: + break; + + case MMC_POWER_UP: + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd); + + host->ferror = sunxi_mmc_init_host(mmc); + if (host->ferror) + return; + + dev_dbg(mmc_dev(mmc), "power on!\n"); + break; + + case MMC_POWER_OFF: + dev_dbg(mmc_dev(mmc), "power off!\n"); + sunxi_mmc_reset_host(host); + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); + break; + } + + /* set bus width */ + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + mmc_writel(host, REG_WIDTH, SDXC_WIDTH1); + break; + case MMC_BUS_WIDTH_4: + mmc_writel(host, REG_WIDTH, SDXC_WIDTH4); + break; + case MMC_BUS_WIDTH_8: + mmc_writel(host, REG_WIDTH, SDXC_WIDTH8); + break; + } + + /* set ddr mode */ + rval = mmc_readl(host, REG_GCTRL); + if (ios->timing == MMC_TIMING_UHS_DDR50) + rval |= SDXC_DDR_MODE; + else + rval &= ~SDXC_DDR_MODE; + mmc_writel(host, REG_GCTRL, rval); + + /* set up clock */ + if (ios->clock && ios->power_mode) { + host->ferror = sunxi_mmc_clk_set_rate(host, ios); + /* Android code had a usleep_range(50000, 55000); here */ + } +} + +static void sunxi_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct sunxi_mmc_host *host = mmc_priv(mmc); + unsigned long flags; + u32 imask; + + spin_lock_irqsave(&host->lock, flags); + + imask = mmc_readl(host, REG_IMASK); + if (enable) { + host->sdio_imask = SDXC_SDIO_INTERRUPT; + imask |= SDXC_SDIO_INTERRUPT; + } else { + host->sdio_imask = 0; + imask &= ~SDXC_SDIO_INTERRUPT; + } + mmc_writel(host, REG_IMASK, imask); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void sunxi_mmc_hw_reset(struct mmc_host *mmc) +{ + struct sunxi_mmc_host *host = mmc_priv(mmc); + mmc_writel(host, REG_HWRST, 0); + udelay(10); + mmc_writel(host, REG_HWRST, 1); + udelay(300); +} + +static void sunxi_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct sunxi_mmc_host *host = mmc_priv(mmc); + struct mmc_command *cmd = mrq->cmd; + struct mmc_data *data = mrq->data; + unsigned long iflags; + u32 imask = SDXC_INTERRUPT_ERROR_BIT; + u32 cmd_val = SDXC_START | (cmd->opcode & 0x3f); + int ret; + + /* Check for set_ios errors (should never happen) */ + if (host->ferror) { + mrq->cmd->error = host->ferror; + mmc_request_done(mmc, mrq); + return; + } + + if (data) { + ret = sunxi_mmc_map_dma(host, data); + if (ret < 0) { + dev_err(mmc_dev(mmc), "map DMA failed\n"); + cmd->error = ret; + data->error = ret; + mmc_request_done(mmc, mrq); + return; + } + } + + if (cmd->opcode == MMC_GO_IDLE_STATE) { + cmd_val |= SDXC_SEND_INIT_SEQUENCE; + imask |= SDXC_COMMAND_DONE; + } + + if (cmd->flags & MMC_RSP_PRESENT) { + cmd_val |= SDXC_RESP_EXPIRE; + if (cmd->flags & MMC_RSP_136) + cmd_val |= SDXC_LONG_RESPONSE; + if (cmd->flags & MMC_RSP_CRC) + cmd_val |= SDXC_CHECK_RESPONSE_CRC; + + if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC) { + cmd_val |= SDXC_DATA_EXPIRE | SDXC_WAIT_PRE_OVER; + if (cmd->data->flags & MMC_DATA_STREAM) { + imask |= SDXC_AUTO_COMMAND_DONE; + cmd_val |= SDXC_SEQUENCE_MODE | + SDXC_SEND_AUTO_STOP; + } + + if (cmd->data->stop) { + imask |= SDXC_AUTO_COMMAND_DONE; + cmd_val |= SDXC_SEND_AUTO_STOP; + } else { + imask |= SDXC_DATA_OVER; + } + + if (cmd->data->flags & MMC_DATA_WRITE) + cmd_val |= SDXC_WRITE; + else + host->wait_dma = true; + } else { + imask |= SDXC_COMMAND_DONE; + } + } else { + imask |= SDXC_COMMAND_DONE; + } + + dev_dbg(mmc_dev(mmc), "cmd %d(%08x) arg %x ie 0x%08x len %d\n", + cmd_val & 0x3f, cmd_val, cmd->arg, imask, + mrq->data ? mrq->data->blksz * mrq->data->blocks : 0); + + spin_lock_irqsave(&host->lock, iflags); + + if (host->mrq || host->manual_stop_mrq) { + spin_unlock_irqrestore(&host->lock, iflags); + + if (data) + dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len, + sunxi_mmc_get_dma_dir(data)); + + dev_err(mmc_dev(mmc), "request already pending\n"); + mrq->cmd->error = -EBUSY; + mmc_request_done(mmc, mrq); + return; + } + + if (data) { + mmc_writel(host, REG_BLKSZ, data->blksz); + mmc_writel(host, REG_BCNTR, data->blksz * data->blocks); + sunxi_mmc_start_dma(host, data); + } + + host->mrq = mrq; + mmc_writel(host, REG_IMASK, host->sdio_imask | imask); + mmc_writel(host, REG_CARG, cmd->arg); + mmc_writel(host, REG_CMDR, cmd_val); + + spin_unlock_irqrestore(&host->lock, iflags); +} + +static const struct of_device_id sunxi_mmc_of_match[] = { + { .compatible = "allwinner,sun4i-a10-mmc", }, + { .compatible = "allwinner,sun5i-a13-mmc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sunxi_mmc_of_match); + +static struct mmc_host_ops sunxi_mmc_ops = { + .request = sunxi_mmc_request, + .set_ios = sunxi_mmc_set_ios, + .get_ro = mmc_gpio_get_ro, + .get_cd = mmc_gpio_get_cd, + .enable_sdio_irq = sunxi_mmc_enable_sdio_irq, + .hw_reset = sunxi_mmc_hw_reset, +}; + +static int sunxi_mmc_resource_request(struct sunxi_mmc_host *host, + struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + int ret; + + if (of_device_is_compatible(np, "allwinner,sun4i-a10-mmc")) + host->idma_des_size_bits = 13; + else + host->idma_des_size_bits = 16; + + ret = mmc_regulator_get_supply(host->mmc); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Could not get vmmc supply\n"); + return ret; + } + + host->reg_base = devm_ioremap_resource(&pdev->dev, + platform_get_resource(pdev, IORESOURCE_MEM, 0)); + if (IS_ERR(host->reg_base)) + return PTR_ERR(host->reg_base); + + host->clk_ahb = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(host->clk_ahb)) { + dev_err(&pdev->dev, "Could not get ahb clock\n"); + return PTR_ERR(host->clk_ahb); + } + + host->clk_mmc = devm_clk_get(&pdev->dev, "mmc"); + if (IS_ERR(host->clk_mmc)) { + dev_err(&pdev->dev, "Could not get mmc clock\n"); + return PTR_ERR(host->clk_mmc); + } + + host->reset = devm_reset_control_get(&pdev->dev, "ahb"); + + ret = clk_prepare_enable(host->clk_ahb); + if (ret) { + dev_err(&pdev->dev, "Enable ahb clk err %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(host->clk_mmc); + if (ret) { + dev_err(&pdev->dev, "Enable mmc clk err %d\n", ret); + goto error_disable_clk_ahb; + } + + if (!IS_ERR(host->reset)) { + ret = reset_control_deassert(host->reset); + if (ret) { + dev_err(&pdev->dev, "reset err %d\n", ret); + goto error_disable_clk_mmc; + } + } + + /* + * Sometimes the controller asserts the irq on boot for some reason, + * make sure the controller is in a sane state before enabling irqs. + */ + ret = sunxi_mmc_reset_host(host); + if (ret) + goto error_assert_reset; + + host->irq = platform_get_irq(pdev, 0); + return devm_request_threaded_irq(&pdev->dev, host->irq, sunxi_mmc_irq, + sunxi_mmc_handle_manual_stop, 0, "sunxi-mmc", host); + +error_assert_reset: + if (!IS_ERR(host->reset)) + reset_control_assert(host->reset); +error_disable_clk_mmc: + clk_disable_unprepare(host->clk_mmc); +error_disable_clk_ahb: + clk_disable_unprepare(host->clk_ahb); + return ret; +} + +static int sunxi_mmc_probe(struct platform_device *pdev) +{ + struct sunxi_mmc_host *host; + struct mmc_host *mmc; + int ret; + + mmc = mmc_alloc_host(sizeof(struct sunxi_mmc_host), &pdev->dev); + if (!mmc) { + dev_err(&pdev->dev, "mmc alloc host failed\n"); + return -ENOMEM; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + spin_lock_init(&host->lock); + + ret = sunxi_mmc_resource_request(host, pdev); + if (ret) + goto error_free_host; + + host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, + &host->sg_dma, GFP_KERNEL); + if (!host->sg_cpu) { + dev_err(&pdev->dev, "Failed to allocate DMA descriptor mem\n"); + ret = -ENOMEM; + goto error_free_host; + } + + mmc->ops = &sunxi_mmc_ops; + mmc->max_blk_count = 8192; + mmc->max_blk_size = 4096; + mmc->max_segs = PAGE_SIZE / sizeof(struct sunxi_idma_des); + mmc->max_seg_size = (1 << host->idma_des_size_bits); + mmc->max_req_size = mmc->max_seg_size * mmc->max_segs; + /* 400kHz ~ 50MHz */ + mmc->f_min = 400000; + mmc->f_max = 50000000; + mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; + + ret = mmc_of_parse(mmc); + if (ret) + goto error_free_dma; + + ret = mmc_add_host(mmc); + if (ret) + goto error_free_dma; + + dev_info(&pdev->dev, "base:0x%p irq:%u\n", host->reg_base, host->irq); + platform_set_drvdata(pdev, mmc); + return 0; + +error_free_dma: + dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); +error_free_host: + mmc_free_host(mmc); + return ret; +} + +static int sunxi_mmc_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct sunxi_mmc_host *host = mmc_priv(mmc); + + mmc_remove_host(mmc); + disable_irq(host->irq); + sunxi_mmc_reset_host(host); + + if (!IS_ERR(host->reset)) + reset_control_assert(host->reset); + + clk_disable_unprepare(host->clk_mmc); + clk_disable_unprepare(host->clk_ahb); + + dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); + mmc_free_host(mmc); + + return 0; +} + +static struct platform_driver sunxi_mmc_driver = { + .driver = { + .name = "sunxi-mmc", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sunxi_mmc_of_match), + }, + .probe = sunxi_mmc_probe, + .remove = sunxi_mmc_remove, +}; +module_platform_driver(sunxi_mmc_driver); + +MODULE_DESCRIPTION("Allwinner's SD/MMC Card Controller Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("David Lanzend�rfer "); +MODULE_ALIAS("platform:sunxi-mmc"); -- cgit v1.2.3