diff options
Diffstat (limited to 'drivers')
82 files changed, 9709 insertions, 1443 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index c6c2eb14db..d6fbcbfe16 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -39,5 +39,6 @@ source "drivers/phy/Kconfig" source "drivers/crypto/Kconfig" source "drivers/memory/Kconfig" source "drivers/soc/imx/Kconfig" +source "drivers/nvme/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 752fd66242..65fd488ce9 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -39,3 +39,4 @@ obj-$(CONFIG_CRYPTO_HW) += crypto/ obj-$(CONFIG_AIODEV) += aiodev/ obj-y += memory/ obj-y += soc/imx/ +obj-y += nvme/ diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 1fd6bbc014..eec2a2d8a2 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -439,12 +439,6 @@ int generic_memmap_ro(struct cdev *cdev, void **map, int flags) return generic_memmap_rw(cdev, map, flags); } -int dummy_probe(struct device_d *dev) -{ - return 0; -} -EXPORT_SYMBOL(dummy_probe); - /** * dev_set_name - set a device name * @dev: device diff --git a/drivers/block/efi-block-io.c b/drivers/block/efi-block-io.c index 2bbeb99e69..d167d814c2 100644 --- a/drivers/block/efi-block-io.c +++ b/drivers/block/efi-block-io.c @@ -142,7 +142,7 @@ static int is_bio_usbdev(struct efi_device *efidev) return 0; } -int efi_bio_probe(struct efi_device *efidev) +static int efi_bio_probe(struct efi_device *efidev) { int ret; struct efi_bio_priv *priv; diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index cd4026c944..34c44fff9b 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -1,7 +1,8 @@ obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed.o clk-divider.o clk-fixed-factor.o \ clk-mux.o clk-gate.o clk-composite.o \ clk-fractional-divider.o clk-conf.o \ - clk-gate-shared.o clk-gpio.o + clk-gate-shared.o clk-gpio.o \ + clk-bulk.o obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o obj-$(CONFIG_ARCH_MVEBU) += mvebu/ diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile index 13e67bd35c..bf9b27f0f4 100644 --- a/drivers/clk/at91/Makefile +++ b/drivers/clk/at91/Makefile @@ -11,3 +11,9 @@ obj-$(CONFIG_HAVE_AT91_USB_CLK) += clk-usb.o obj-$(CONFIG_HAVE_AT91_SMD) += clk-smd.o obj-$(CONFIG_HAVE_AT91_H32MX) += clk-h32mx.o obj-$(CONFIG_HAVE_AT91_GENERATED_CLK) += clk-generated.o +obj-$(CONFIG_SOC_AT91SAM9) += at91sam9260.o +obj-$(CONFIG_SOC_AT91SAM9) += at91sam9rl.o +obj-$(CONFIG_SOC_AT91SAM9) += at91sam9x5.o +obj-$(CONFIG_SOC_SAMA5D2) += sama5d2.o +obj-$(CONFIG_SOC_SAMA5D3) += dt-compat.o +obj-$(CONFIG_SOC_SAMA5D4) += sama5d4.o diff --git a/drivers/clk/at91/at91sam9260.c b/drivers/clk/at91/at91sam9260.c new file mode 100644 index 0000000000..ac67dcc8f7 --- /dev/null +++ b/drivers/clk/at91/at91sam9260.c @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <driver.h> +#include <regmap.h> +#include <stdio.h> +#include <mfd/syscon.h> + +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <dt-bindings/clock/at91.h> + +#include "pmc.h" + +struct sck { + char *n; + char *p; + u8 id; +}; + +struct pck { + char *n; + u8 id; +}; + +struct at91sam926x_data { + const struct clk_pll_layout *plla_layout; + const struct clk_pll_characteristics *plla_characteristics; + const struct clk_pll_layout *pllb_layout; + const struct clk_pll_characteristics *pllb_characteristics; + const struct clk_master_characteristics *mck_characteristics; + const struct sck *sck; + const struct pck *pck; + u8 num_sck; + u8 num_pck; + u8 num_progck; + bool has_slck; +}; + +static const struct clk_master_characteristics sam9260_mck_characteristics = { + .output = { .min = 0, .max = 105000000 }, + .divisors = { 1, 2, 4, 0 }, +}; + +static u8 sam9260_plla_out[] = { 0, 2 }; + +static u16 sam9260_plla_icpll[] = { 1, 1 }; + +static struct clk_range sam9260_plla_outputs[] = { + { .min = 80000000, .max = 160000000 }, + { .min = 150000000, .max = 240000000 }, +}; + +static const struct clk_pll_characteristics sam9260_plla_characteristics = { + .input = { .min = 1000000, .max = 32000000 }, + .num_output = ARRAY_SIZE(sam9260_plla_outputs), + .output = sam9260_plla_outputs, + .icpll = sam9260_plla_icpll, + .out = sam9260_plla_out, +}; + +static u8 sam9260_pllb_out[] = { 1 }; + +static u16 sam9260_pllb_icpll[] = { 1 }; + +static struct clk_range sam9260_pllb_outputs[] = { + { .min = 70000000, .max = 130000000 }, +}; + +static const struct clk_pll_characteristics sam9260_pllb_characteristics = { + .input = { .min = 1000000, .max = 5000000 }, + .num_output = ARRAY_SIZE(sam9260_pllb_outputs), + .output = sam9260_pllb_outputs, + .icpll = sam9260_pllb_icpll, + .out = sam9260_pllb_out, +}; + +static const struct sck at91sam9260_systemck[] = { + { .n = "uhpck", .p = "usbck", .id = 6 }, + { .n = "udpck", .p = "usbck", .id = 7 }, + { .n = "pck0", .p = "prog0", .id = 8 }, + { .n = "pck1", .p = "prog1", .id = 9 }, +}; + +static const struct pck at91sam9260_periphck[] = { + { .n = "pioA_clk", .id = 2 }, + { .n = "pioB_clk", .id = 3 }, + { .n = "pioC_clk", .id = 4 }, + { .n = "adc_clk", .id = 5 }, + { .n = "usart0_clk", .id = 6 }, + { .n = "usart1_clk", .id = 7 }, + { .n = "usart2_clk", .id = 8 }, + { .n = "mci0_clk", .id = 9 }, + { .n = "udc_clk", .id = 10 }, + { .n = "twi0_clk", .id = 11 }, + { .n = "spi0_clk", .id = 12 }, + { .n = "spi1_clk", .id = 13 }, + { .n = "ssc0_clk", .id = 14 }, + { .n = "tc0_clk", .id = 17 }, + { .n = "tc1_clk", .id = 18 }, + { .n = "tc2_clk", .id = 19 }, + { .n = "ohci_clk", .id = 20 }, + { .n = "macb0_clk", .id = 21 }, + { .n = "isi_clk", .id = 22 }, + { .n = "usart3_clk", .id = 23 }, + { .n = "uart0_clk", .id = 24 }, + { .n = "uart1_clk", .id = 25 }, + { .n = "tc3_clk", .id = 26 }, + { .n = "tc4_clk", .id = 27 }, + { .n = "tc5_clk", .id = 28 }, +}; + +static struct at91sam926x_data at91sam9260_data = { + .plla_layout = &at91rm9200_pll_layout, + .plla_characteristics = &sam9260_plla_characteristics, + .pllb_layout = &at91rm9200_pll_layout, + .pllb_characteristics = &sam9260_pllb_characteristics, + .mck_characteristics = &sam9260_mck_characteristics, + .sck = at91sam9260_systemck, + .num_sck = ARRAY_SIZE(at91sam9260_systemck), + .pck = at91sam9260_periphck, + .num_pck = ARRAY_SIZE(at91sam9260_periphck), + .num_progck = 2, + .has_slck = true, +}; + +static const struct clk_master_characteristics sam9g20_mck_characteristics = { + .output = { .min = 0, .max = 133000000 }, + .divisors = { 1, 2, 4, 6 }, +}; + +static u8 sam9g20_plla_out[] = { 0, 1, 2, 3, 0, 1, 2, 3 }; + +static u16 sam9g20_plla_icpll[] = { 0, 0, 0, 0, 1, 1, 1, 1 }; + +static struct clk_range sam9g20_plla_outputs[] = { + { .min = 745000000, .max = 800000000 }, + { .min = 695000000, .max = 750000000 }, + { .min = 645000000, .max = 700000000 }, + { .min = 595000000, .max = 650000000 }, + { .min = 545000000, .max = 600000000 }, + { .min = 495000000, .max = 550000000 }, + { .min = 445000000, .max = 500000000 }, + { .min = 400000000, .max = 450000000 }, +}; + +static const struct clk_pll_characteristics sam9g20_plla_characteristics = { + .input = { .min = 2000000, .max = 32000000 }, + .num_output = ARRAY_SIZE(sam9g20_plla_outputs), + .output = sam9g20_plla_outputs, + .icpll = sam9g20_plla_icpll, + .out = sam9g20_plla_out, +}; + +static u8 sam9g20_pllb_out[] = { 0 }; + +static u16 sam9g20_pllb_icpll[] = { 0 }; + +static struct clk_range sam9g20_pllb_outputs[] = { + { .min = 30000000, .max = 100000000 }, +}; + +static const struct clk_pll_characteristics sam9g20_pllb_characteristics = { + .input = { .min = 2000000, .max = 32000000 }, + .num_output = ARRAY_SIZE(sam9g20_pllb_outputs), + .output = sam9g20_pllb_outputs, + .icpll = sam9g20_pllb_icpll, + .out = sam9g20_pllb_out, +}; + +static struct at91sam926x_data at91sam9g20_data = { + .plla_layout = &at91sam9g45_pll_layout, + .plla_characteristics = &sam9g20_plla_characteristics, + .pllb_layout = &at91sam9g20_pllb_layout, + .pllb_characteristics = &sam9g20_pllb_characteristics, + .mck_characteristics = &sam9g20_mck_characteristics, + .sck = at91sam9260_systemck, + .num_sck = ARRAY_SIZE(at91sam9260_systemck), + .pck = at91sam9260_periphck, + .num_pck = ARRAY_SIZE(at91sam9260_periphck), + .num_progck = 2, + .has_slck = true, +}; + +static const struct clk_master_characteristics sam9261_mck_characteristics = { + .output = { .min = 0, .max = 94000000 }, + .divisors = { 1, 2, 4, 0 }, +}; + +static struct clk_range sam9261_plla_outputs[] = { + { .min = 80000000, .max = 200000000 }, + { .min = 190000000, .max = 240000000 }, +}; + +static const struct clk_pll_characteristics sam9261_plla_characteristics = { + .input = { .min = 1000000, .max = 32000000 }, + .num_output = ARRAY_SIZE(sam9261_plla_outputs), + .output = sam9261_plla_outputs, + .icpll = sam9260_plla_icpll, + .out = sam9260_plla_out, +}; + +static u8 sam9261_pllb_out[] = { 1 }; + +static u16 sam9261_pllb_icpll[] = { 1 }; + +static struct clk_range sam9261_pllb_outputs[] = { + { .min = 70000000, .max = 130000000 }, +}; + +static const struct clk_pll_characteristics sam9261_pllb_characteristics = { + .input = { .min = 1000000, .max = 5000000 }, + .num_output = ARRAY_SIZE(sam9261_pllb_outputs), + .output = sam9261_pllb_outputs, + .icpll = sam9261_pllb_icpll, + .out = sam9261_pllb_out, +}; + +static const struct sck at91sam9261_systemck[] = { + { .n = "uhpck", .p = "usbck", .id = 6 }, + { .n = "udpck", .p = "usbck", .id = 7 }, + { .n = "pck0", .p = "prog0", .id = 8 }, + { .n = "pck1", .p = "prog1", .id = 9 }, + { .n = "pck2", .p = "prog2", .id = 10 }, + { .n = "pck3", .p = "prog3", .id = 11 }, + { .n = "hclk0", .p = "masterck", .id = 16 }, + { .n = "hclk1", .p = "masterck", .id = 17 }, +}; + +static const struct pck at91sam9261_periphck[] = { + { .n = "pioA_clk", .id = 2, }, + { .n = "pioB_clk", .id = 3, }, + { .n = "pioC_clk", .id = 4, }, + { .n = "usart0_clk", .id = 6, }, + { .n = "usart1_clk", .id = 7, }, + { .n = "usart2_clk", .id = 8, }, + { .n = "mci0_clk", .id = 9, }, + { .n = "udc_clk", .id = 10, }, + { .n = "twi0_clk", .id = 11, }, + { .n = "spi0_clk", .id = 12, }, + { .n = "spi1_clk", .id = 13, }, + { .n = "ssc0_clk", .id = 14, }, + { .n = "ssc1_clk", .id = 15, }, + { .n = "ssc2_clk", .id = 16, }, + { .n = "tc0_clk", .id = 17, }, + { .n = "tc1_clk", .id = 18, }, + { .n = "tc2_clk", .id = 19, }, + { .n = "ohci_clk", .id = 20, }, + { .n = "lcd_clk", .id = 21, }, +}; + +static struct at91sam926x_data at91sam9261_data = { + .plla_layout = &at91rm9200_pll_layout, + .plla_characteristics = &sam9261_plla_characteristics, + .pllb_layout = &at91rm9200_pll_layout, + .pllb_characteristics = &sam9261_pllb_characteristics, + .mck_characteristics = &sam9261_mck_characteristics, + .sck = at91sam9261_systemck, + .num_sck = ARRAY_SIZE(at91sam9261_systemck), + .pck = at91sam9261_periphck, + .num_pck = ARRAY_SIZE(at91sam9261_periphck), + .num_progck = 4, +}; + +static const struct clk_master_characteristics sam9263_mck_characteristics = { + .output = { .min = 0, .max = 120000000 }, + .divisors = { 1, 2, 4, 0 }, +}; + +static struct clk_range sam9263_pll_outputs[] = { + { .min = 80000000, .max = 200000000 }, + { .min = 190000000, .max = 240000000 }, +}; + +static const struct clk_pll_characteristics sam9263_pll_characteristics = { + .input = { .min = 1000000, .max = 32000000 }, + .num_output = ARRAY_SIZE(sam9263_pll_outputs), + .output = sam9263_pll_outputs, + .icpll = sam9260_plla_icpll, + .out = sam9260_plla_out, +}; + +static const struct sck at91sam9263_systemck[] = { + { .n = "uhpck", .p = "usbck", .id = 6 }, + { .n = "udpck", .p = "usbck", .id = 7 }, + { .n = "pck0", .p = "prog0", .id = 8 }, + { .n = "pck1", .p = "prog1", .id = 9 }, + { .n = "pck2", .p = "prog2", .id = 10 }, + { .n = "pck3", .p = "prog3", .id = 11 }, +}; + +static const struct pck at91sam9263_periphck[] = { + { .n = "pioA_clk", .id = 2, }, + { .n = "pioB_clk", .id = 3, }, + { .n = "pioCDE_clk", .id = 4, }, + { .n = "usart0_clk", .id = 7, }, + { .n = "usart1_clk", .id = 8, }, + { .n = "usart2_clk", .id = 9, }, + { .n = "mci0_clk", .id = 10, }, + { .n = "mci1_clk", .id = 11, }, + { .n = "can_clk", .id = 12, }, + { .n = "twi0_clk", .id = 13, }, + { .n = "spi0_clk", .id = 14, }, + { .n = "spi1_clk", .id = 15, }, + { .n = "ssc0_clk", .id = 16, }, + { .n = "ssc1_clk", .id = 17, }, + { .n = "ac97_clk", .id = 18, }, + { .n = "tcb_clk", .id = 19, }, + { .n = "pwm_clk", .id = 20, }, + { .n = "macb0_clk", .id = 21, }, + { .n = "g2de_clk", .id = 23, }, + { .n = "udc_clk", .id = 24, }, + { .n = "isi_clk", .id = 25, }, + { .n = "lcd_clk", .id = 26, }, + { .n = "dma_clk", .id = 27, }, + { .n = "ohci_clk", .id = 29, }, +}; + +static struct at91sam926x_data at91sam9263_data = { + .plla_layout = &at91rm9200_pll_layout, + .plla_characteristics = &sam9263_pll_characteristics, + .pllb_layout = &at91rm9200_pll_layout, + .pllb_characteristics = &sam9263_pll_characteristics, + .mck_characteristics = &sam9263_mck_characteristics, + .sck = at91sam9263_systemck, + .num_sck = ARRAY_SIZE(at91sam9263_systemck), + .pck = at91sam9263_periphck, + .num_pck = ARRAY_SIZE(at91sam9263_periphck), + .num_progck = 4, +}; + +static void __init at91sam926x_pmc_setup(struct device_node *np, + struct at91sam926x_data *data) +{ + const char *slowxtal_name, *mainxtal_name; + struct pmc_data *at91sam9260_pmc; + u32 usb_div[] = { 1, 2, 4, 0 }; + const char *parent_names[6]; + const char *slck_name; + struct regmap *regmap; + struct clk *hw; + int i; + bool bypass; + + i = of_property_match_string(np, "clock-names", "slow_xtal"); + if (i < 0) + return; + + slowxtal_name = of_clk_get_parent_name(np, i); + + i = of_property_match_string(np, "clock-names", "main_xtal"); + if (i < 0) + return; + mainxtal_name = of_clk_get_parent_name(np, i); + + regmap = syscon_node_to_regmap(np); + if (IS_ERR(regmap)) + return; + + at91sam9260_pmc = pmc_data_allocate(PMC_MAIN + 1, + ndck(data->sck, data->num_sck), + ndck(data->pck, data->num_pck), 0); + if (!at91sam9260_pmc) + return; + + bypass = of_property_read_bool(np, "atmel,osc-bypass"); + + hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name, + bypass); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_rm9200_main(regmap, "mainck", "main_osc"); + if (IS_ERR(hw)) + goto err_free; + + at91sam9260_pmc->chws[PMC_MAIN] = hw; + + if (data->has_slck) { + hw = clk_fixed("slow_rc_osc", 32768); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = "slow_rc_osc"; + parent_names[1] = "slow_xtal"; + hw = at91_clk_register_sam9260_slow(regmap, "slck", + parent_names, 2); + if (IS_ERR(hw)) + goto err_free; + + at91sam9260_pmc->chws[PMC_SLOW] = hw; + slck_name = "slck"; + } else { + slck_name = slowxtal_name; + } + + hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0, + data->plla_layout, + data->plla_characteristics); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_pll(regmap, "pllbck", "mainck", 1, + data->pllb_layout, + data->pllb_characteristics); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "pllack"; + parent_names[3] = "pllbck"; + hw = at91_clk_register_master(regmap, "masterck", 4, parent_names, + &at91rm9200_master_layout, + data->mck_characteristics); + if (IS_ERR(hw)) + goto err_free; + + at91sam9260_pmc->chws[PMC_MCK] = hw; + + hw = at91rm9200_clk_register_usb(regmap, "usbck", "pllbck", usb_div); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "pllack"; + parent_names[3] = "pllbck"; + for (i = 0; i < data->num_progck; i++) { + char *name; + + name = xasprintf("prog%d", i); + + hw = at91_clk_register_programmable(regmap, name, + parent_names, 4, i, + &at91rm9200_programmable_layout); + if (IS_ERR(hw)) + goto err_free; + } + + for (i = 0; i < data->num_sck; i++) { + hw = at91_clk_register_system(regmap, data->sck[i].n, + data->sck[i].p, + data->sck[i].id); + if (IS_ERR(hw)) + goto err_free; + + at91sam9260_pmc->shws[data->sck[i].id] = hw; + } + + for (i = 0; i < data->num_pck; i++) { + hw = at91_clk_register_peripheral(regmap, + data->pck[i].n, + "masterck", + data->pck[i].id); + if (IS_ERR(hw)) + goto err_free; + + at91sam9260_pmc->phws[data->pck[i].id] = hw; + } + + of_clk_add_provider(np, of_clk_hw_pmc_get, at91sam9260_pmc); + + return; + +err_free: + pmc_data_free(at91sam9260_pmc); +} + +static void __init at91sam9260_pmc_setup(struct device_node *np) +{ + at91sam926x_pmc_setup(np, &at91sam9260_data); +} +CLK_OF_DECLARE_DRIVER(at91sam9260_pmc, "atmel,at91sam9260-pmc", + at91sam9260_pmc_setup); + +static void __init at91sam9261_pmc_setup(struct device_node *np) +{ + at91sam926x_pmc_setup(np, &at91sam9261_data); +} +CLK_OF_DECLARE_DRIVER(at91sam9261_pmc, "atmel,at91sam9261-pmc", + at91sam9261_pmc_setup); + +static void __init at91sam9263_pmc_setup(struct device_node *np) +{ + at91sam926x_pmc_setup(np, &at91sam9263_data); +} +CLK_OF_DECLARE_DRIVER(at91sam9263_pmc, "atmel,at91sam9263-pmc", + at91sam9263_pmc_setup); + +static void __init at91sam9g20_pmc_setup(struct device_node *np) +{ + at91sam926x_pmc_setup(np, &at91sam9g20_data); +} +CLK_OF_DECLARE_DRIVER(at91sam9g20_pmc, "atmel,at91sam9g20-pmc", + at91sam9g20_pmc_setup); diff --git a/drivers/clk/at91/at91sam9rl.c b/drivers/clk/at91/at91sam9rl.c new file mode 100644 index 0000000000..82acb38257 --- /dev/null +++ b/drivers/clk/at91/at91sam9rl.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <driver.h> +#include <regmap.h> +#include <stdio.h> +#include <mfd/syscon.h> + +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <dt-bindings/clock/at91.h> + +#include "pmc.h" + +static const struct clk_master_characteristics sam9rl_mck_characteristics = { + .output = { .min = 0, .max = 94000000 }, + .divisors = { 1, 2, 4, 0 }, +}; + +static u8 sam9rl_plla_out[] = { 0, 2 }; + +static struct clk_range sam9rl_plla_outputs[] = { + { .min = 80000000, .max = 200000000 }, + { .min = 190000000, .max = 240000000 }, +}; + +static const struct clk_pll_characteristics sam9rl_plla_characteristics = { + .input = { .min = 1000000, .max = 32000000 }, + .num_output = ARRAY_SIZE(sam9rl_plla_outputs), + .output = sam9rl_plla_outputs, + .out = sam9rl_plla_out, +}; + +static const struct { + char *n; + char *p; + u8 id; +} at91sam9rl_systemck[] = { + { .n = "pck0", .p = "prog0", .id = 8 }, + { .n = "pck1", .p = "prog1", .id = 9 }, +}; + +static const struct { + char *n; + u8 id; +} at91sam9rl_periphck[] = { + { .n = "pioA_clk", .id = 2, }, + { .n = "pioB_clk", .id = 3, }, + { .n = "pioC_clk", .id = 4, }, + { .n = "pioD_clk", .id = 5, }, + { .n = "usart0_clk", .id = 6, }, + { .n = "usart1_clk", .id = 7, }, + { .n = "usart2_clk", .id = 8, }, + { .n = "usart3_clk", .id = 9, }, + { .n = "mci0_clk", .id = 10, }, + { .n = "twi0_clk", .id = 11, }, + { .n = "twi1_clk", .id = 12, }, + { .n = "spi0_clk", .id = 13, }, + { .n = "ssc0_clk", .id = 14, }, + { .n = "ssc1_clk", .id = 15, }, + { .n = "tc0_clk", .id = 16, }, + { .n = "tc1_clk", .id = 17, }, + { .n = "tc2_clk", .id = 18, }, + { .n = "pwm_clk", .id = 19, }, + { .n = "adc_clk", .id = 20, }, + { .n = "dma0_clk", .id = 21, }, + { .n = "udphs_clk", .id = 22, }, + { .n = "lcd_clk", .id = 23, }, +}; + +static void __init at91sam9rl_pmc_setup(struct device_node *np) +{ + const char *slck_name, *mainxtal_name; + struct pmc_data *at91sam9rl_pmc; + const char *parent_names[6]; + struct regmap *regmap; + struct clk *hw; + int i; + + i = of_property_match_string(np, "clock-names", "slow_clk"); + if (i < 0) + return; + + slck_name = of_clk_get_parent_name(np, i); + + i = of_property_match_string(np, "clock-names", "main_xtal"); + if (i < 0) + return; + mainxtal_name = of_clk_get_parent_name(np, i); + + regmap = syscon_node_to_regmap(np); + if (IS_ERR(regmap)) + return; + + at91sam9rl_pmc = pmc_data_allocate(PMC_MAIN + 1, + nck(at91sam9rl_systemck), + nck(at91sam9rl_periphck), 0); + if (!at91sam9rl_pmc) + return; + + hw = at91_clk_register_rm9200_main(regmap, "mainck", mainxtal_name); + if (IS_ERR(hw)) + goto err_free; + + at91sam9rl_pmc->chws[PMC_MAIN] = hw; + + hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0, + &at91rm9200_pll_layout, + &sam9rl_plla_characteristics); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_utmi(regmap, NULL, "utmick", "mainck"); + if (IS_ERR(hw)) + goto err_free; + + at91sam9rl_pmc->chws[PMC_UTMI] = hw; + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "pllack"; + parent_names[3] = "utmick"; + hw = at91_clk_register_master(regmap, "masterck", 4, parent_names, + &at91rm9200_master_layout, + &sam9rl_mck_characteristics); + if (IS_ERR(hw)) + goto err_free; + + at91sam9rl_pmc->chws[PMC_MCK] = hw; + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "pllack"; + parent_names[3] = "utmick"; + parent_names[4] = "masterck"; + for (i = 0; i < 2; i++) { + char *name; + + name = xasprintf("prog%d", i); + + hw = at91_clk_register_programmable(regmap, name, + parent_names, 5, i, + &at91rm9200_programmable_layout); + if (IS_ERR(hw)) + goto err_free; + } + + for (i = 0; i < ARRAY_SIZE(at91sam9rl_systemck); i++) { + hw = at91_clk_register_system(regmap, at91sam9rl_systemck[i].n, + at91sam9rl_systemck[i].p, + at91sam9rl_systemck[i].id); + if (IS_ERR(hw)) + goto err_free; + + at91sam9rl_pmc->shws[at91sam9rl_systemck[i].id] = hw; + } + + for (i = 0; i < ARRAY_SIZE(at91sam9rl_periphck); i++) { + hw = at91_clk_register_peripheral(regmap, + at91sam9rl_periphck[i].n, + "masterck", + at91sam9rl_periphck[i].id); + if (IS_ERR(hw)) + goto err_free; + + at91sam9rl_pmc->phws[at91sam9rl_periphck[i].id] = hw; + } + + of_clk_add_provider(np, of_clk_hw_pmc_get, at91sam9rl_pmc); + + return; + +err_free: + pmc_data_free(at91sam9rl_pmc); +} +CLK_OF_DECLARE_DRIVER(at91sam9rl_pmc, "atmel,at91sam9rl-pmc", at91sam9rl_pmc_setup); diff --git a/drivers/clk/at91/at91sam9x5.c b/drivers/clk/at91/at91sam9x5.c new file mode 100644 index 0000000000..5e0aacfbf6 --- /dev/null +++ b/drivers/clk/at91/at91sam9x5.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <driver.h> +#include <regmap.h> +#include <stdio.h> +#include <mfd/syscon.h> + +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <dt-bindings/clock/at91.h> + +#include "pmc.h" + +static const struct clk_master_characteristics mck_characteristics = { + .output = { .min = 0, .max = 133333333 }, + .divisors = { 1, 2, 4, 3 }, + .have_div3_pres = 1, +}; + +static u8 plla_out[] = { 0, 1, 2, 3, 0, 1, 2, 3 }; + +static u16 plla_icpll[] = { 0, 0, 0, 0, 1, 1, 1, 1 }; + +static struct clk_range plla_outputs[] = { + { .min = 745000000, .max = 800000000 }, + { .min = 695000000, .max = 750000000 }, + { .min = 645000000, .max = 700000000 }, + { .min = 595000000, .max = 650000000 }, + { .min = 545000000, .max = 600000000 }, + { .min = 495000000, .max = 555000000 }, + { .min = 445000000, .max = 500000000 }, + { .min = 400000000, .max = 450000000 }, +}; + +static const struct clk_pll_characteristics plla_characteristics = { + .input = { .min = 2000000, .max = 32000000 }, + .num_output = ARRAY_SIZE(plla_outputs), + .output = plla_outputs, + .icpll = plla_icpll, + .out = plla_out, +}; + +static const struct { + char *n; + char *p; + u8 id; +} at91sam9x5_systemck[] = { + { .n = "ddrck", .p = "masterck", .id = 2 }, + { .n = "smdck", .p = "smdclk", .id = 4 }, + { .n = "uhpck", .p = "usbck", .id = 6 }, + { .n = "udpck", .p = "usbck", .id = 7 }, + { .n = "pck0", .p = "prog0", .id = 8 }, + { .n = "pck1", .p = "prog1", .id = 9 }, +}; + +struct pck { + char *n; + u8 id; +}; + +static const struct pck at91sam9x5_periphck[] = { + { .n = "pioAB_clk", .id = 2, }, + { .n = "pioCD_clk", .id = 3, }, + { .n = "smd_clk", .id = 4, }, + { .n = "usart0_clk", .id = 5, }, + { .n = "usart1_clk", .id = 6, }, + { .n = "usart2_clk", .id = 7, }, + { .n = "twi0_clk", .id = 9, }, + { .n = "twi1_clk", .id = 10, }, + { .n = "twi2_clk", .id = 11, }, + { .n = "mci0_clk", .id = 12, }, + { .n = "spi0_clk", .id = 13, }, + { .n = "spi1_clk", .id = 14, }, + { .n = "uart0_clk", .id = 15, }, + { .n = "uart1_clk", .id = 16, }, + { .n = "tcb0_clk", .id = 17, }, + { .n = "pwm_clk", .id = 18, }, + { .n = "adc_clk", .id = 19, }, + { .n = "dma0_clk", .id = 20, }, + { .n = "dma1_clk", .id = 21, }, + { .n = "uhphs_clk", .id = 22, }, + { .n = "udphs_clk", .id = 23, }, + { .n = "mci1_clk", .id = 26, }, + { .n = "ssc0_clk", .id = 28, }, +}; + +static const struct pck at91sam9g15_periphck[] = { + { .n = "lcdc_clk", .id = 25, }, + { /* sentinel */} +}; + +static const struct pck at91sam9g25_periphck[] = { + { .n = "usart3_clk", .id = 8, }, + { .n = "macb0_clk", .id = 24, }, + { .n = "isi_clk", .id = 25, }, + { /* sentinel */} +}; + +static const struct pck at91sam9g35_periphck[] = { + { .n = "macb0_clk", .id = 24, }, + { .n = "lcdc_clk", .id = 25, }, + { /* sentinel */} +}; + +static const struct pck at91sam9x25_periphck[] = { + { .n = "usart3_clk", .id = 8, }, + { .n = "macb0_clk", .id = 24, }, + { .n = "macb1_clk", .id = 27, }, + { .n = "can0_clk", .id = 29, }, + { .n = "can1_clk", .id = 30, }, + { /* sentinel */} +}; + +static const struct pck at91sam9x35_periphck[] = { + { .n = "macb0_clk", .id = 24, }, + { .n = "lcdc_clk", .id = 25, }, + { .n = "can0_clk", .id = 29, }, + { .n = "can1_clk", .id = 30, }, + { /* sentinel */} +}; + +static void __init at91sam9x5_pmc_setup(struct device_node *np, + const struct pck *extra_pcks, + bool has_lcdck) +{ + struct clk_range range = CLK_RANGE(0, 0); + const char *slck_name, *mainxtal_name; + struct pmc_data *at91sam9x5_pmc; + const char *parent_names[6]; + struct regmap *regmap; + struct clk *hw; + int i; + bool bypass; + + i = of_property_match_string(np, "clock-names", "slow_clk"); + if (i < 0) + return; + + slck_name = of_clk_get_parent_name(np, i); + + i = of_property_match_string(np, "clock-names", "main_xtal"); + if (i < 0) + return; + mainxtal_name = of_clk_get_parent_name(np, i); + + regmap = syscon_node_to_regmap(np); + if (IS_ERR(regmap)) + return; + + at91sam9x5_pmc = pmc_data_allocate(PMC_MAIN + 1, + nck(at91sam9x5_systemck), + nck(at91sam9x35_periphck), 0); + if (!at91sam9x5_pmc) + return; + + hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000, + 50000000); + if (IS_ERR(hw)) + goto err_free; + + bypass = of_property_read_bool(np, "atmel,osc-bypass"); + + hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name, + bypass); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = "main_rc_osc"; + parent_names[1] = "main_osc"; + hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2); + if (IS_ERR(hw)) + goto err_free; + + at91sam9x5_pmc->chws[PMC_MAIN] = hw; + + hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0, + &at91rm9200_pll_layout, &plla_characteristics); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_plldiv(regmap, "plladivck", "pllack"); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_utmi(regmap, NULL, "utmick", "mainck"); + if (IS_ERR(hw)) + goto err_free; + + at91sam9x5_pmc->chws[PMC_UTMI] = hw; + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "plladivck"; + parent_names[3] = "utmick"; + hw = at91_clk_register_master(regmap, "masterck", 4, parent_names, + &at91sam9x5_master_layout, + &mck_characteristics); + if (IS_ERR(hw)) + goto err_free; + + at91sam9x5_pmc->chws[PMC_MCK] = hw; + + parent_names[0] = "plladivck"; + parent_names[1] = "utmick"; + hw = at91sam9x5_clk_register_usb(regmap, "usbck", parent_names, 2); + if (IS_ERR(hw)) + goto err_free; + + hw = at91sam9x5_clk_register_smd(regmap, "smdclk", parent_names, 2); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "plladivck"; + parent_names[3] = "utmick"; + parent_names[4] = "mck"; + for (i = 0; i < 2; i++) { + char *name; + + name = xasprintf("prog%d", i); + + hw = at91_clk_register_programmable(regmap, name, + parent_names, 5, i, + &at91sam9x5_programmable_layout); + if (IS_ERR(hw)) + goto err_free; + } + + for (i = 0; i < ARRAY_SIZE(at91sam9x5_systemck); i++) { + hw = at91_clk_register_system(regmap, at91sam9x5_systemck[i].n, + at91sam9x5_systemck[i].p, + at91sam9x5_systemck[i].id); + if (IS_ERR(hw)) + goto err_free; + + at91sam9x5_pmc->shws[at91sam9x5_systemck[i].id] = hw; + } + + if (has_lcdck) { + hw = at91_clk_register_system(regmap, "lcdck", "masterck", 3); + if (IS_ERR(hw)) + goto err_free; + + at91sam9x5_pmc->shws[3] = hw; + } + + for (i = 0; i < ARRAY_SIZE(at91sam9x5_periphck); i++) { + hw = at91_clk_register_sam9x5_peripheral(regmap, + at91sam9x5_periphck[i].n, + "masterck", + at91sam9x5_periphck[i].id, + &range); + if (IS_ERR(hw)) + goto err_free; + + at91sam9x5_pmc->phws[at91sam9x5_periphck[i].id] = hw; + } + + for (i = 0; extra_pcks[i].id; i++) { + hw = at91_clk_register_sam9x5_peripheral(regmap, + extra_pcks[i].n, + "masterck", + extra_pcks[i].id, + &range); + if (IS_ERR(hw)) + goto err_free; + + at91sam9x5_pmc->phws[extra_pcks[i].id] = hw; + } + + of_clk_add_provider(np, of_clk_hw_pmc_get, at91sam9x5_pmc); + + return; + +err_free: + pmc_data_free(at91sam9x5_pmc); +} + +static void __init at91sam9g15_pmc_setup(struct device_node *np) +{ + at91sam9x5_pmc_setup(np, at91sam9g15_periphck, true); +} +CLK_OF_DECLARE_DRIVER(at91sam9g15_pmc, "atmel,at91sam9g15-pmc", + at91sam9g15_pmc_setup); + +static void __init at91sam9g25_pmc_setup(struct device_node *np) +{ + at91sam9x5_pmc_setup(np, at91sam9g25_periphck, false); +} +CLK_OF_DECLARE_DRIVER(at91sam9g25_pmc, "atmel,at91sam9g25-pmc", + at91sam9g25_pmc_setup); + +static void __init at91sam9g35_pmc_setup(struct device_node *np) +{ + at91sam9x5_pmc_setup(np, at91sam9g35_periphck, true); +} +CLK_OF_DECLARE_DRIVER(at91sam9g35_pmc, "atmel,at91sam9g35-pmc", + at91sam9g35_pmc_setup); + +static void __init at91sam9x25_pmc_setup(struct device_node *np) +{ + at91sam9x5_pmc_setup(np, at91sam9x25_periphck, false); +} +CLK_OF_DECLARE_DRIVER(at91sam9x25_pmc, "atmel,at91sam9x25-pmc", + at91sam9x25_pmc_setup); + +static void __init at91sam9x35_pmc_setup(struct device_node *np) +{ + at91sam9x5_pmc_setup(np, at91sam9x35_periphck, true); +} +CLK_OF_DECLARE_DRIVER(at91sam9x35_pmc, "atmel,at91sam9x35-pmc", + at91sam9x35_pmc_setup); diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c index 4e1cd5aa69..60516ca10f 100644 --- a/drivers/clk/at91/clk-generated.c +++ b/drivers/clk/at91/clk-generated.c @@ -11,26 +11,23 @@ * */ -#include <linux/clk-provider.h> -#include <linux/clkdev.h> +#include <common.h> +#include <clock.h> +#include <io.h> +#include <linux/list.h> +#include <linux/clk.h> #include <linux/clk/at91_pmc.h> -#include <linux/of.h> -#include <linux/mfd/syscon.h> -#include <linux/regmap.h> +#include <mfd/syscon.h> +#include <regmap.h> #include "pmc.h" -#define PERIPHERAL_MAX 64 -#define PERIPHERAL_ID_MIN 2 - -#define GENERATED_SOURCE_MAX 6 #define GENERATED_MAX_DIV 255 struct clk_generated { - struct clk_hw hw; + struct clk hw; struct regmap *regmap; struct clk_range range; - spinlock_t *lock; u32 id; u32 gckdiv; u8 parent_id; @@ -39,15 +36,13 @@ struct clk_generated { #define to_clk_generated(hw) \ container_of(hw, struct clk_generated, hw) -static int clk_generated_enable(struct clk_hw *hw) +static int clk_generated_enable(struct clk *hw) { struct clk_generated *gck = to_clk_generated(hw); - unsigned long flags; pr_debug("GCLK: %s, gckdiv = %d, parent id = %d\n", __func__, gck->gckdiv, gck->parent_id); - spin_lock_irqsave(gck->lock, flags); regmap_write(gck->regmap, AT91_PMC_PCR, (gck->id & AT91_PMC_PCR_PID_MASK)); regmap_update_bits(gck->regmap, AT91_PMC_PCR, @@ -57,41 +52,34 @@ static int clk_generated_enable(struct clk_hw *hw) AT91_PMC_PCR_CMD | AT91_PMC_PCR_GCKDIV(gck->gckdiv) | AT91_PMC_PCR_GCKEN); - spin_unlock_irqrestore(gck->lock, flags); return 0; } -static void clk_generated_disable(struct clk_hw *hw) +static void clk_generated_disable(struct clk *hw) { struct clk_generated *gck = to_clk_generated(hw); - unsigned long flags; - spin_lock_irqsave(gck->lock, flags); regmap_write(gck->regmap, AT91_PMC_PCR, (gck->id & AT91_PMC_PCR_PID_MASK)); regmap_update_bits(gck->regmap, AT91_PMC_PCR, AT91_PMC_PCR_CMD | AT91_PMC_PCR_GCKEN, AT91_PMC_PCR_CMD); - spin_unlock_irqrestore(gck->lock, flags); } -static int clk_generated_is_enabled(struct clk_hw *hw) +static int clk_generated_is_enabled(struct clk *hw) { struct clk_generated *gck = to_clk_generated(hw); - unsigned long flags; unsigned int status; - spin_lock_irqsave(gck->lock, flags); regmap_write(gck->regmap, AT91_PMC_PCR, (gck->id & AT91_PMC_PCR_PID_MASK)); regmap_read(gck->regmap, AT91_PMC_PCR, &status); - spin_unlock_irqrestore(gck->lock, flags); return status & AT91_PMC_PCR_GCKEN ? 1 : 0; } static unsigned long -clk_generated_recalc_rate(struct clk_hw *hw, +clk_generated_recalc_rate(struct clk *hw, unsigned long parent_rate) { struct clk_generated *gck = to_clk_generated(hw); @@ -99,75 +87,19 @@ clk_generated_recalc_rate(struct clk_hw *hw, return DIV_ROUND_CLOSEST(parent_rate, gck->gckdiv + 1); } -static int clk_generated_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - struct clk_generated *gck = to_clk_generated(hw); - struct clk_hw *parent = NULL; - long best_rate = -EINVAL; - unsigned long tmp_rate, min_rate; - int best_diff = -1; - int tmp_diff; - int i; - - for (i = 0; i < clk_hw_get_num_parents(hw); i++) { - u32 div; - unsigned long parent_rate; - - parent = clk_hw_get_parent_by_index(hw, i); - if (!parent) - continue; - - parent_rate = clk_hw_get_rate(parent); - min_rate = DIV_ROUND_CLOSEST(parent_rate, GENERATED_MAX_DIV + 1); - if (!parent_rate || - (gck->range.max && min_rate > gck->range.max)) - continue; - - for (div = 1; div < GENERATED_MAX_DIV + 2; div++) { - tmp_rate = DIV_ROUND_CLOSEST(parent_rate, div); - tmp_diff = abs(req->rate - tmp_rate); - - if (best_diff < 0 || best_diff > tmp_diff) { - best_rate = tmp_rate; - best_diff = tmp_diff; - req->best_parent_rate = parent_rate; - req->best_parent_hw = parent; - } - - if (!best_diff || tmp_rate < req->rate) - break; - } - - if (!best_diff) - break; - } - - pr_debug("GCLK: %s, best_rate = %ld, parent clk: %s @ %ld\n", - __func__, best_rate, - __clk_get_name((req->best_parent_hw)->clk), - req->best_parent_rate); - - if (best_rate < 0) - return best_rate; - - req->rate = best_rate; - return 0; -} - /* No modification of hardware as we have the flag CLK_SET_PARENT_GATE set */ -static int clk_generated_set_parent(struct clk_hw *hw, u8 index) +static int clk_generated_set_parent(struct clk *hw, u8 index) { struct clk_generated *gck = to_clk_generated(hw); - if (index >= clk_hw_get_num_parents(hw)) + if (index >= clk_get_num_parents(hw)) return -EINVAL; gck->parent_id = index; return 0; } -static u8 clk_generated_get_parent(struct clk_hw *hw) +static int clk_generated_get_parent(struct clk *hw) { struct clk_generated *gck = to_clk_generated(hw); @@ -175,7 +107,7 @@ static u8 clk_generated_get_parent(struct clk_hw *hw) } /* No modification of hardware as we have the flag CLK_SET_RATE_GATE set */ -static int clk_generated_set_rate(struct clk_hw *hw, +static int clk_generated_set_rate(struct clk *hw, unsigned long rate, unsigned long parent_rate) { @@ -201,7 +133,6 @@ static const struct clk_ops generated_ops = { .disable = clk_generated_disable, .is_enabled = clk_generated_is_enabled, .recalc_rate = clk_generated_recalc_rate, - .determine_rate = clk_generated_determine_rate, .get_parent = clk_generated_get_parent, .set_parent = clk_generated_set_parent, .set_rate = clk_generated_set_rate, @@ -219,13 +150,10 @@ static const struct clk_ops generated_ops = { static void clk_generated_startup(struct clk_generated *gck) { u32 tmp; - unsigned long flags; - spin_lock_irqsave(gck->lock, flags); regmap_write(gck->regmap, AT91_PMC_PCR, (gck->id & AT91_PMC_PCR_PID_MASK)); regmap_read(gck->regmap, AT91_PMC_PCR, &tmp); - spin_unlock_irqrestore(gck->lock, flags); gck->parent_id = (tmp & AT91_PMC_PCR_GCKCSS_MASK) >> AT91_PMC_PCR_GCKCSS_OFFSET; @@ -233,35 +161,37 @@ static void clk_generated_startup(struct clk_generated *gck) >> AT91_PMC_PCR_GCKDIV_OFFSET; } -static struct clk_hw * __init -at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock, +struct clk * __init +at91_clk_register_generated(struct regmap *regmap, const char *name, const char **parent_names, - u8 num_parents, u8 id, + u8 num_parents, u8 id, bool pll_audio, const struct clk_range *range) { + size_t parents_array_size; struct clk_generated *gck; - struct clk_init_data init; - struct clk_hw *hw; + struct clk *hw; int ret; gck = kzalloc(sizeof(*gck), GFP_KERNEL); if (!gck) return ERR_PTR(-ENOMEM); - init.name = name; - init.ops = &generated_ops; - init.parent_names = parent_names; - init.num_parents = num_parents; - init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; - gck->id = id; - gck->hw.init = &init; + gck->hw.name = name; + gck->hw.ops = &generated_ops; + + parents_array_size = num_parents * sizeof(gck->hw.parent_names[0]); + gck->hw.parent_names = xzalloc(parents_array_size); + memcpy(gck->hw.parent_names, parent_names, parents_array_size); + gck->hw.num_parents = num_parents; + + /* gck->hw.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; */ gck->regmap = regmap; - gck->lock = lock; gck->range = *range; + /* gck->audio_pll_allowed = pll_audio; */ hw = &gck->hw; - ret = clk_hw_register(NULL, &gck->hw); + ret = clk_register(&gck->hw); if (ret) { kfree(gck); hw = ERR_PTR(ret); @@ -270,54 +200,3 @@ at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock, return hw; } - -static void __init of_sama5d2_clk_generated_setup(struct device_node *np) -{ - int num; - u32 id; - const char *name; - struct clk_hw *hw; - unsigned int num_parents; - const char *parent_names[GENERATED_SOURCE_MAX]; - struct device_node *gcknp; - struct clk_range range = CLK_RANGE(0, 0); - struct regmap *regmap; - - num_parents = of_clk_get_parent_count(np); - if (num_parents == 0 || num_parents > GENERATED_SOURCE_MAX) - return; - - of_clk_parent_fill(np, parent_names, num_parents); - - num = of_get_child_count(np); - if (!num || num > PERIPHERAL_MAX) - return; - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return; - - for_each_child_of_node(np, gcknp) { - if (of_property_read_u32(gcknp, "reg", &id)) - continue; - - if (id < PERIPHERAL_ID_MIN || id >= PERIPHERAL_MAX) - continue; - - if (of_property_read_string(np, "clock-output-names", &name)) - name = gcknp->name; - - of_at91_get_clk_range(gcknp, "atmel,clk-output-range", - &range); - - hw = at91_clk_register_generated(regmap, &pmc_pcr_lock, name, - parent_names, num_parents, - id, &range); - if (IS_ERR(hw)) - continue; - - of_clk_add_hw_provider(gcknp, of_clk_hw_simple_get, hw); - } -} -CLK_OF_DECLARE(of_sama5d2_clk_generated_setup, "atmel,sama5d2-clk-generated", - of_sama5d2_clk_generated_setup); diff --git a/drivers/clk/at91/clk-h32mx.c b/drivers/clk/at91/clk-h32mx.c index e0daa4a31f..31906a9e29 100644 --- a/drivers/clk/at91/clk-h32mx.c +++ b/drivers/clk/at91/clk-h32mx.c @@ -12,25 +12,27 @@ * */ -#include <linux/clk-provider.h> -#include <linux/clkdev.h> +#include <common.h> +#include <clock.h> +#include <linux/list.h> +#include <linux/clk.h> #include <linux/clk/at91_pmc.h> -#include <linux/of.h> -#include <linux/regmap.h> -#include <linux/mfd/syscon.h> +#include <regmap.h> + #include "pmc.h" #define H32MX_MAX_FREQ 90000000 struct clk_sama5d4_h32mx { - struct clk_hw hw; + struct clk hw; struct regmap *regmap; + const char *parent; }; #define to_clk_sama5d4_h32mx(hw) container_of(hw, struct clk_sama5d4_h32mx, hw) -static unsigned long clk_sama5d4_h32mx_recalc_rate(struct clk_hw *hw, +static unsigned long clk_sama5d4_h32mx_recalc_rate(struct clk *hw, unsigned long parent_rate) { struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw); @@ -45,7 +47,7 @@ static unsigned long clk_sama5d4_h32mx_recalc_rate(struct clk_hw *hw, return parent_rate; } -static long clk_sama5d4_h32mx_round_rate(struct clk_hw *hw, unsigned long rate, +static long clk_sama5d4_h32mx_round_rate(struct clk *hw, unsigned long rate, unsigned long *parent_rate) { unsigned long div; @@ -62,7 +64,7 @@ static long clk_sama5d4_h32mx_round_rate(struct clk_hw *hw, unsigned long rate, return *parent_rate; } -static int clk_sama5d4_h32mx_set_rate(struct clk_hw *hw, unsigned long rate, +static int clk_sama5d4_h32mx_set_rate(struct clk *hw, unsigned long rate, unsigned long parent_rate) { struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw); @@ -86,40 +88,31 @@ static const struct clk_ops h32mx_ops = { .set_rate = clk_sama5d4_h32mx_set_rate, }; -static void __init of_sama5d4_clk_h32mx_setup(struct device_node *np) +struct clk * +at91_clk_register_h32mx(struct regmap *regmap, const char *name, + const char *parent_name) { struct clk_sama5d4_h32mx *h32mxclk; - struct clk_init_data init; - const char *parent_name; - struct regmap *regmap; int ret; - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return; - h32mxclk = kzalloc(sizeof(*h32mxclk), GFP_KERNEL); if (!h32mxclk) - return; - - parent_name = of_clk_get_parent_name(np, 0); + return ERR_PTR(-ENOMEM); - init.name = np->name; - init.ops = &h32mx_ops; - init.parent_names = parent_name ? &parent_name : NULL; - init.num_parents = parent_name ? 1 : 0; - init.flags = CLK_SET_RATE_GATE; + h32mxclk->parent = parent_name; + h32mxclk->hw.name = name; + h32mxclk->hw.ops = &h32mx_ops; + h32mxclk->hw.parent_names = &h32mxclk->parent; + h32mxclk->hw.num_parents = 1; + /* h32mxclk.hw.flags = CLK_SET_RATE_GATE; */ - h32mxclk->hw.init = &init; h32mxclk->regmap = regmap; - ret = clk_hw_register(NULL, &h32mxclk->hw); + ret = clk_register(&h32mxclk->hw); if (ret) { kfree(h32mxclk); - return; + return ERR_PTR(ret); } - of_clk_add_hw_provider(np, of_clk_hw_simple_get, &h32mxclk->hw); + return &h32mxclk->hw; } -CLK_OF_DECLARE(of_sama5d4_clk_h32mx_setup, "atmel,sama5d4-clk-h32mx", - of_sama5d4_clk_h32mx_setup); diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c index 77dfdef518..4d4127dd00 100644 --- a/drivers/clk/at91/clk-main.c +++ b/drivers/clk/at91/clk-main.c @@ -9,7 +9,6 @@ */ #include <common.h> #include <clock.h> -#include <of.h> #include <linux/list.h> #include <linux/clk.h> #include <linux/clk/at91_pmc.h> @@ -129,7 +128,7 @@ static const struct clk_ops main_osc_ops = { .is_enabled = clk_main_osc_is_enabled, }; -static struct clk * +struct clk * at91_clk_register_main_osc(struct regmap *regmap, const char *name, const char *parent_name, @@ -165,31 +164,6 @@ at91_clk_register_main_osc(struct regmap *regmap, return &osc->clk; } -static int of_at91rm9200_clk_main_osc_setup(struct device_node *np) -{ - struct clk *clk; - const char *name = np->name; - const char *parent_name; - struct regmap *regmap; - bool bypass; - - of_property_read_string(np, "clock-output-names", &name); - bypass = of_property_read_bool(np, "atmel,osc-bypass"); - parent_name = of_clk_get_parent_name(np, 0); - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - clk = at91_clk_register_main_osc(regmap, name, parent_name, bypass); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} -CLK_OF_DECLARE(at91rm9200_clk_main_osc, "atmel,at91rm9200-clk-main-osc", - of_at91rm9200_clk_main_osc_setup); - static bool clk_main_rc_osc_ready(struct regmap *regmap) { unsigned int status; @@ -260,10 +234,10 @@ static const struct clk_ops main_rc_osc_ops = { .recalc_rate = clk_main_rc_osc_recalc_rate, }; -static struct clk * +struct clk * at91_clk_register_main_rc_osc(struct regmap *regmap, const char *name, - u32 frequency) + u32 frequency, u32 accuracy) { int ret; struct clk_main_rc_osc *osc; @@ -290,30 +264,6 @@ at91_clk_register_main_rc_osc(struct regmap *regmap, return &osc->clk; } -static int of_at91sam9x5_clk_main_rc_osc_setup(struct device_node *np) -{ - struct clk *clk; - u32 frequency = 0; - const char *name = np->name; - struct regmap *regmap; - - of_property_read_string(np, "clock-output-names", &name); - of_property_read_u32(np, "clock-frequency", &frequency); - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - clk = at91_clk_register_main_rc_osc(regmap, name, frequency); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} -CLK_OF_DECLARE(at91sam9x5_clk_main_rc_osc, "atmel,at91sam9x5-clk-main-rc-osc", - of_at91sam9x5_clk_main_rc_osc_setup); - - static int clk_main_probe_frequency(struct regmap *regmap) { unsigned int mcfr; @@ -375,7 +325,7 @@ static const struct clk_ops rm9200_main_ops = { .recalc_rate = clk_rm9200_main_recalc_rate, }; -static struct clk * +struct clk * at91_clk_register_rm9200_main(struct regmap *regmap, const char *name, const char *parent_name) @@ -407,29 +357,6 @@ at91_clk_register_rm9200_main(struct regmap *regmap, return &clkmain->clk; } -static int of_at91rm9200_clk_main_setup(struct device_node *np) -{ - struct clk *clk; - const char *parent_name; - const char *name = np->name; - struct regmap *regmap; - - parent_name = of_clk_get_parent_name(np, 0); - of_property_read_string(np, "clock-output-names", &name); - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - clk = at91_clk_register_rm9200_main(regmap, name, parent_name); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} -CLK_OF_DECLARE(at91rm9200_clk_main, "atmel,at91rm9200-clk-main", - of_at91rm9200_clk_main_setup); - static inline bool clk_sam9x5_main_ready(struct regmap *regmap) { unsigned int status; @@ -506,7 +433,7 @@ static const struct clk_ops sam9x5_main_ops = { .get_parent = clk_sam9x5_main_get_parent, }; -static struct clk * +struct clk * at91_clk_register_sam9x5_main(struct regmap *regmap, const char *name, const char **parent_names, @@ -546,32 +473,3 @@ at91_clk_register_sam9x5_main(struct regmap *regmap, return &clkmain->clk; } - -static int of_at91sam9x5_clk_main_setup(struct device_node *np) -{ - struct clk *clk; - const char *parent_names[2]; - unsigned int num_parents; - const char *name = np->name; - struct regmap *regmap; - - num_parents = of_clk_get_parent_count(np); - if (num_parents == 0 || num_parents > 2) - return -EINVAL; - - of_clk_parent_fill(np, parent_names, num_parents); - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - of_property_read_string(np, "clock-output-names", &name); - - clk = at91_clk_register_sam9x5_main(regmap, name, parent_names, - num_parents); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} -CLK_OF_DECLARE(at91sam9x5_clk_main, "atmel,at91sam9x5-clk-main", - of_at91sam9x5_clk_main_setup); diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c index b3a50ce542..f7a0fb1d18 100644 --- a/drivers/clk/at91/clk-master.c +++ b/drivers/clk/at91/clk-master.c @@ -9,7 +9,6 @@ */ #include <common.h> #include <clock.h> -#include <of.h> #include <linux/list.h> #include <linux/clk.h> #include <linux/clk/at91_pmc.h> @@ -25,17 +24,6 @@ #define MASTER_DIV_SHIFT 8 #define MASTER_DIV_MASK 0x3 -struct clk_master_characteristics { - struct clk_range output; - u32 divisors[4]; - u8 have_div3_pres; -}; - -struct clk_master_layout { - u32 mask; - u8 pres_shift; -}; - #define to_clk_master(clk) container_of(clk, struct clk_master, clk) struct clk_master { @@ -122,7 +110,7 @@ static const struct clk_ops master_ops = { .get_parent = clk_master_get_parent, }; -static struct clk * +struct clk * at91_clk_register_master(struct regmap *regmap, const char *name, int num_parents, const char **parent_names, @@ -158,88 +146,12 @@ at91_clk_register_master(struct regmap *regmap, } -static const struct clk_master_layout at91rm9200_master_layout = { +const struct clk_master_layout at91rm9200_master_layout = { .mask = 0x31F, .pres_shift = 2, }; -static const struct clk_master_layout at91sam9x5_master_layout = { +const struct clk_master_layout at91sam9x5_master_layout = { .mask = 0x373, .pres_shift = 4, }; - - -static struct clk_master_characteristics * -of_at91_clk_master_get_characteristics(struct device_node *np) -{ - struct clk_master_characteristics *characteristics; - - characteristics = xzalloc(sizeof(*characteristics)); - - if (of_at91_get_clk_range(np, "atmel,clk-output-range", &characteristics->output)) - goto out_free_characteristics; - - of_property_read_u32_array(np, "atmel,clk-divisors", - characteristics->divisors, 4); - - characteristics->have_div3_pres = - of_property_read_bool(np, "atmel,master-clk-have-div3-pres"); - - return characteristics; - -out_free_characteristics: - kfree(characteristics); - return NULL; -} - -static int -of_at91_clk_master_setup(struct device_node *np, - const struct clk_master_layout *layout) -{ - struct clk *clk; - unsigned int num_parents; - const char *parent_names[MASTER_SOURCE_MAX]; - const char *name = np->name; - struct clk_master_characteristics *characteristics; - struct regmap *regmap; - - num_parents = of_clk_get_parent_count(np); - if (num_parents == 0 || num_parents > MASTER_SOURCE_MAX) - return -EINVAL; - - of_clk_parent_fill(np, parent_names, num_parents); - - of_property_read_string(np, "clock-output-names", &name); - - characteristics = of_at91_clk_master_get_characteristics(np); - if (!characteristics) - return -EINVAL; - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - clk = at91_clk_register_master(regmap, name, num_parents, - parent_names, layout, - characteristics); - if (IS_ERR(clk)) { - kfree(characteristics); - return PTR_ERR(clk); - } - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} - -static void __init of_at91rm9200_clk_master_setup(struct device_node *np) -{ - of_at91_clk_master_setup(np, &at91rm9200_master_layout); -} -CLK_OF_DECLARE(at91rm9200_clk_master, "atmel,at91rm9200-clk-master", - of_at91rm9200_clk_master_setup); - -static void __init of_at91sam9x5_clk_master_setup(struct device_node *np) -{ - of_at91_clk_master_setup(np, &at91sam9x5_master_layout); -} -CLK_OF_DECLARE(at91sam9x5_clk_master, "atmel,at91sam9x5-clk-master", - of_at91sam9x5_clk_master_setup); diff --git a/drivers/clk/at91/clk-peripheral.c b/drivers/clk/at91/clk-peripheral.c index bbe6ffac69..00852672da 100644 --- a/drivers/clk/at91/clk-peripheral.c +++ b/drivers/clk/at91/clk-peripheral.c @@ -10,7 +10,6 @@ #include <common.h> #include <clock.h> -#include <of.h> #include <linux/list.h> #include <linux/clk.h> #include <linux/clk/at91_pmc.h> @@ -19,11 +18,6 @@ #include "pmc.h" -#define PERIPHERAL_MAX 64 - -#define PERIPHERAL_AT91RM9200 0 -#define PERIPHERAL_AT91SAM9X5 1 - #define PERIPHERAL_ID_MIN 2 #define PERIPHERAL_ID_MAX 31 #define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX)) @@ -105,7 +99,7 @@ static const struct clk_ops peripheral_ops = { .is_enabled = clk_peripheral_is_enabled, }; -static struct clk * +struct clk * at91_clk_register_peripheral(struct regmap *regmap, const char *name, const char *parent_name, u32 id) { @@ -317,7 +311,7 @@ static const struct clk_ops sam9x5_peripheral_ops = { .set_rate = clk_sam9x5_peripheral_set_rate, }; -static struct clk * +struct clk * at91_clk_register_sam9x5_peripheral(struct regmap *regmap, const char *name, const char *parent_name, u32 id, const struct clk_range *range) @@ -355,75 +349,3 @@ at91_clk_register_sam9x5_peripheral(struct regmap *regmap, return &periph->clk; } - -static int -of_at91_clk_periph_setup(struct device_node *np, u8 type) -{ - int num; - u32 id; - struct clk *clk; - const char *parent_name; - const char *name; - struct device_node *periphclknp; - struct regmap *regmap; - - parent_name = of_clk_get_parent_name(np, 0); - if (!parent_name) - return -ENOENT; - - num = of_get_child_count(np); - if (!num || num > PERIPHERAL_MAX) - return -EINVAL; - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - for_each_child_of_node(np, periphclknp) { - if (of_property_read_u32(periphclknp, "reg", &id)) - continue; - - if (id >= PERIPHERAL_MAX) - continue; - - if (of_property_read_string(np, "clock-output-names", &name)) - name = periphclknp->name; - - if (type == PERIPHERAL_AT91RM9200) { - clk = at91_clk_register_peripheral(regmap, name, - parent_name, id); - } else { - struct clk_range range = CLK_RANGE(0, 0); - - of_at91_get_clk_range(periphclknp, - "atmel,clk-output-range", - &range); - - clk = at91_clk_register_sam9x5_peripheral(regmap, - name, - parent_name, - id, &range); - } - - if (IS_ERR(clk)) - continue; - - of_clk_add_provider(periphclknp, of_clk_src_simple_get, clk); - } - - return 0; -} - -static int of_at91rm9200_clk_periph_setup(struct device_node *np) -{ - return of_at91_clk_periph_setup(np, PERIPHERAL_AT91RM9200); -} -CLK_OF_DECLARE(at91rm9200_clk_periph, "atmel,at91rm9200-clk-peripheral", - of_at91rm9200_clk_periph_setup); - -static int of_at91sam9x5_clk_periph_setup(struct device_node *np) -{ - return of_at91_clk_periph_setup(np, PERIPHERAL_AT91SAM9X5); -} -CLK_OF_DECLARE(at91sam9x5_clk_periph, "atmel,at91sam9x5-clk-peripheral", - of_at91sam9x5_clk_periph_setup); diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c index e0af4fe5a8..bc504e8a95 100644 --- a/drivers/clk/at91/clk-pll.c +++ b/drivers/clk/at91/clk-pll.c @@ -36,20 +36,6 @@ #define PLL_OUT_SHIFT 14 #define PLL_MAX_ID 1 -struct clk_pll_characteristics { - struct clk_range input; - int num_output; - struct clk_range *output; - u16 *icpll; - u8 *out; -}; - -struct clk_pll_layout { - u32 pllr_mask; - u16 mul_mask; - u8 mul_shift; -}; - #define to_clk_pll(clk) container_of(clk, struct clk_pll, clk) struct clk_pll { @@ -299,7 +285,7 @@ static const struct clk_ops pll_ops = { .set_rate = clk_pll_set_rate, }; -static struct clk * +struct clk * at91_clk_register_pll(struct regmap *regmap, const char *name, const char *parent_name, u8 id, const struct clk_pll_layout *layout, @@ -341,176 +327,26 @@ at91_clk_register_pll(struct regmap *regmap, const char *name, } -static const struct clk_pll_layout at91rm9200_pll_layout = { +const struct clk_pll_layout at91rm9200_pll_layout = { .pllr_mask = 0x7FFFFFF, .mul_shift = 16, .mul_mask = 0x7FF, }; -static const struct clk_pll_layout at91sam9g45_pll_layout = { +const struct clk_pll_layout at91sam9g45_pll_layout = { .pllr_mask = 0xFFFFFF, .mul_shift = 16, .mul_mask = 0xFF, }; -static const struct clk_pll_layout at91sam9g20_pllb_layout = { +const struct clk_pll_layout at91sam9g20_pllb_layout = { .pllr_mask = 0x3FFFFF, .mul_shift = 16, .mul_mask = 0x3F, }; -static const struct clk_pll_layout sama5d3_pll_layout = { +const struct clk_pll_layout sama5d3_pll_layout = { .pllr_mask = 0x1FFFFFF, .mul_shift = 18, .mul_mask = 0x7F, }; - - -static struct clk_pll_characteristics * -of_at91_clk_pll_get_characteristics(struct device_node *np) -{ - int i; - int offset; - u32 tmp; - int num_output; - u32 num_cells; - struct clk_range input; - struct clk_range *output; - u8 *out = NULL; - u16 *icpll = NULL; - struct clk_pll_characteristics *characteristics; - - if (of_at91_get_clk_range(np, "atmel,clk-input-range", &input)) - return NULL; - - if (of_property_read_u32(np, "#atmel,pll-clk-output-range-cells", - &num_cells)) - return NULL; - - if (num_cells < 2 || num_cells > 4) - return NULL; - - if (!of_get_property(np, "atmel,pll-clk-output-ranges", &tmp)) - return NULL; - num_output = tmp / (sizeof(u32) * num_cells); - - characteristics = xzalloc(sizeof(*characteristics)); - output = xzalloc(sizeof(*output) * num_output); - - if (num_cells > 2) - out = xzalloc(sizeof(*out) * num_output); - - if (num_cells > 3) - icpll = xzalloc(sizeof(*icpll) * num_output); - - - for (i = 0; i < num_output; i++) { - offset = i * num_cells; - if (of_property_read_u32_index(np, - "atmel,pll-clk-output-ranges", - offset, &tmp)) - goto out_free_output; - output[i].min = tmp; - if (of_property_read_u32_index(np, - "atmel,pll-clk-output-ranges", - offset + 1, &tmp)) - goto out_free_output; - output[i].max = tmp; - - if (num_cells == 2) - continue; - - if (of_property_read_u32_index(np, - "atmel,pll-clk-output-ranges", - offset + 2, &tmp)) - goto out_free_output; - out[i] = tmp; - - if (num_cells == 3) - continue; - - if (of_property_read_u32_index(np, - "atmel,pll-clk-output-ranges", - offset + 3, &tmp)) - goto out_free_output; - icpll[i] = tmp; - } - - characteristics->input = input; - characteristics->num_output = num_output; - characteristics->output = output; - characteristics->out = out; - characteristics->icpll = icpll; - return characteristics; - -out_free_output: - kfree(icpll); - kfree(out); - kfree(output); - kfree(characteristics); - return NULL; -} - -static int -of_at91_clk_pll_setup(struct device_node *np, - const struct clk_pll_layout *layout) -{ - u32 id; - struct clk *clk; - struct regmap *regmap; - const char *parent_name; - const char *name = np->name; - struct clk_pll_characteristics *characteristics; - - if (of_property_read_u32(np, "reg", &id)) - return -EINVAL; - - parent_name = of_clk_get_parent_name(np, 0); - - of_property_read_string(np, "clock-output-names", &name); - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - characteristics = of_at91_clk_pll_get_characteristics(np); - if (!characteristics) - return -EINVAL; - - clk = at91_clk_register_pll(regmap, name, parent_name, id, layout, - characteristics); - if (IS_ERR(clk)) { - kfree(characteristics); - return PTR_ERR(clk); - } - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} - -static int of_at91rm9200_clk_pll_setup(struct device_node *np) -{ - return of_at91_clk_pll_setup(np, &at91rm9200_pll_layout); -} -CLK_OF_DECLARE(at91rm9200_clk_pll, "atmel,at91rm9200-clk-pll", - of_at91rm9200_clk_pll_setup); - -static int of_at91sam9g45_clk_pll_setup(struct device_node *np) -{ - return of_at91_clk_pll_setup(np, &at91sam9g45_pll_layout); -} -CLK_OF_DECLARE(at91sam9g45_clk_pll, "atmel,at91sam9g45-clk-pll", - of_at91sam9g45_clk_pll_setup); - -static int of_at91sam9g20_clk_pllb_setup(struct device_node *np) -{ - return of_at91_clk_pll_setup(np, &at91sam9g20_pllb_layout); -} -CLK_OF_DECLARE(at91sam9g20_clk_pllb, "atmel,at91sam9g20-clk-pllb", - of_at91sam9g20_clk_pllb_setup); - -static int of_sama5d3_clk_pll_setup(struct device_node *np) -{ - return of_at91_clk_pll_setup(np, &sama5d3_pll_layout); -} -CLK_OF_DECLARE(sama5d3_clk_pll, "atmel,sama5d3-clk-pll", - of_sama5d3_clk_pll_setup); diff --git a/drivers/clk/at91/clk-plldiv.c b/drivers/clk/at91/clk-plldiv.c index 917108e84c..98d79ef599 100644 --- a/drivers/clk/at91/clk-plldiv.c +++ b/drivers/clk/at91/clk-plldiv.c @@ -78,7 +78,7 @@ static const struct clk_ops plldiv_ops = { .set_rate = clk_plldiv_set_rate, }; -static struct clk * +struct clk * at91_clk_register_plldiv(struct regmap *regmap, const char *name, const char *parent_name) { @@ -108,28 +108,3 @@ at91_clk_register_plldiv(struct regmap *regmap, const char *name, return &plldiv->clk; } - -static int -of_at91sam9x5_clk_plldiv_setup(struct device_node *np) -{ - struct clk *clk; - const char *parent_name; - const char *name = np->name; - struct regmap *regmap; - - parent_name = of_clk_get_parent_name(np, 0); - - of_property_read_string(np, "clock-output-names", &name); - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - clk = at91_clk_register_plldiv(regmap, name, parent_name); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} -CLK_OF_DECLARE(at91sam9x5_clk_plldiv, "atmel,at91sam9x5-clk-plldiv", - of_at91sam9x5_clk_plldiv_setup); diff --git a/drivers/clk/at91/clk-programmable.c b/drivers/clk/at91/clk-programmable.c index ddb18c0f7c..857ede1ca9 100644 --- a/drivers/clk/at91/clk-programmable.c +++ b/drivers/clk/at91/clk-programmable.c @@ -10,7 +10,6 @@ #include <common.h> #include <clock.h> -#include <of.h> #include <io.h> #include <linux/list.h> #include <linux/clk.h> @@ -28,12 +27,6 @@ #define PROG_PRES(layout, pckr) ((pckr >> layout->pres_shift) & PROG_PRES_MASK) #define PROG_MAX_RM9200_CSS 3 -struct clk_programmable_layout { - u8 pres_shift; - u8 css_mask; - u8 have_slck_mck; -}; - struct clk_programmable { struct clk clk; struct regmap *regmap; @@ -130,7 +123,7 @@ static const struct clk_ops programmable_ops = { .set_rate = clk_programmable_set_rate, }; -static struct clk * +struct clk * at91_clk_register_programmable(struct regmap *regmap, const char *name, const char **parent_names, u8 num_parents, u8 id, @@ -167,88 +160,20 @@ at91_clk_register_programmable(struct regmap *regmap, return &prog->clk; } -static const struct clk_programmable_layout at91rm9200_programmable_layout = { +const struct clk_programmable_layout at91rm9200_programmable_layout = { .pres_shift = 2, .css_mask = 0x3, .have_slck_mck = 0, }; -static const struct clk_programmable_layout at91sam9g45_programmable_layout = { +const struct clk_programmable_layout at91sam9g45_programmable_layout = { .pres_shift = 2, .css_mask = 0x3, .have_slck_mck = 1, }; -static const struct clk_programmable_layout at91sam9x5_programmable_layout = { +const struct clk_programmable_layout at91sam9x5_programmable_layout = { .pres_shift = 4, .css_mask = 0x7, .have_slck_mck = 0, }; - -static int -of_at91_clk_prog_setup(struct device_node *np, - const struct clk_programmable_layout *layout) -{ - int num; - u32 id; - struct clk *clk; - unsigned int num_parents; - const char *parent_names[PROG_SOURCE_MAX]; - const char *name; - struct device_node *progclknp; - struct regmap *regmap; - - num_parents = of_clk_get_parent_count(np); - if (num_parents == 0 || num_parents > PROG_SOURCE_MAX) - return -EINVAL; - - of_clk_parent_fill(np, parent_names, num_parents); - - num = of_get_child_count(np); - if (!num || num > (PROG_ID_MAX + 1)) - return -EINVAL; - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - for_each_child_of_node(np, progclknp) { - if (of_property_read_u32(progclknp, "reg", &id)) - continue; - - if (of_property_read_string(np, "clock-output-names", &name)) - name = progclknp->name; - - clk = at91_clk_register_programmable(regmap, name, - parent_names, num_parents, - id, layout); - if (IS_ERR(clk)) - continue; - - of_clk_add_provider(progclknp, of_clk_src_simple_get, clk); - } - - return 0; -} - - -static void __init of_at91rm9200_clk_prog_setup(struct device_node *np) -{ - of_at91_clk_prog_setup(np, &at91rm9200_programmable_layout); -} -CLK_OF_DECLARE(at91rm9200_clk_prog, "atmel,at91rm9200-clk-programmable", - of_at91rm9200_clk_prog_setup); - -static int of_at91sam9g45_clk_prog_setup(struct device_node *np) -{ - return of_at91_clk_prog_setup(np, &at91sam9g45_programmable_layout); -} -CLK_OF_DECLARE(at91sam9g45_clk_prog, "atmel,at91sam9g45-clk-programmable", - of_at91sam9g45_clk_prog_setup); - -static int of_at91sam9x5_clk_prog_setup(struct device_node *np) -{ - return of_at91_clk_prog_setup(np, &at91sam9x5_programmable_layout); -} -CLK_OF_DECLARE(at91sam9x5_clk_prog, "atmel,at91sam9x5-clk-programmable", - of_at91sam9x5_clk_prog_setup); diff --git a/drivers/clk/at91/clk-slow.c b/drivers/clk/at91/clk-slow.c index d4981e7b4d..d19f7e15ac 100644 --- a/drivers/clk/at91/clk-slow.c +++ b/drivers/clk/at91/clk-slow.c @@ -12,7 +12,6 @@ #include <common.h> #include <clock.h> -#include <of.h> #include <io.h> #include <linux/list.h> #include <linux/clk.h> @@ -44,7 +43,7 @@ static const struct clk_ops sam9260_slow_ops = { .get_parent = clk_sam9260_slow_get_parent, }; -static struct clk * __init +struct clk * __init at91_clk_register_sam9260_slow(struct regmap *regmap, const char *name, const char **parent_names, @@ -76,33 +75,3 @@ at91_clk_register_sam9260_slow(struct regmap *regmap, return &slowck->clk; } - -static int of_at91sam9260_clk_slow_setup(struct device_node *np) -{ - struct clk *clk; - const char *parent_names[2]; - unsigned int num_parents; - const char *name = np->name; - struct regmap *regmap; - - num_parents = of_clk_get_parent_count(np); - if (num_parents != 2) - return -EINVAL; - - of_clk_parent_fill(np, parent_names, num_parents); - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - of_property_read_string(np, "clock-output-names", &name); - - clk = at91_clk_register_sam9260_slow(regmap, name, parent_names, - num_parents); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} - -CLK_OF_DECLARE(at91sam9260_clk_slow, "atmel,at91sam9260-clk-slow", - of_at91sam9260_clk_slow_setup); diff --git a/drivers/clk/at91/clk-smd.c b/drivers/clk/at91/clk-smd.c index 65c53efbba..e81f0d4d4e 100644 --- a/drivers/clk/at91/clk-smd.c +++ b/drivers/clk/at91/clk-smd.c @@ -10,7 +10,6 @@ #include <common.h> #include <clock.h> -#include <of.h> #include <io.h> #include <linux/list.h> #include <linux/clk.h> @@ -115,7 +114,7 @@ static const struct clk_ops at91sam9x5_smd_ops = { .set_rate = at91sam9x5_clk_smd_set_rate, }; -static struct clk * +struct clk * at91sam9x5_clk_register_smd(struct regmap *regmap, const char *name, const char **parent_names, u8 num_parents) { @@ -140,33 +139,3 @@ at91sam9x5_clk_register_smd(struct regmap *regmap, const char *name, return &smd->clk; } - -static int of_at91sam9x5_clk_smd_setup(struct device_node *np) -{ - struct clk *clk; - unsigned int num_parents; - const char *parent_names[SMD_SOURCE_MAX]; - const char *name = np->name; - struct regmap *regmap; - - num_parents = of_clk_get_parent_count(np); - if (num_parents == 0 || num_parents > SMD_SOURCE_MAX) - return -EINVAL; - - of_clk_parent_fill(np, parent_names, num_parents); - - of_property_read_string(np, "clock-output-names", &name); - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - clk = at91sam9x5_clk_register_smd(regmap, name, parent_names, - num_parents); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} -CLK_OF_DECLARE(at91sam9x5_clk_smd, "atmel,at91sam9x5-clk-smd", - of_at91sam9x5_clk_smd_setup); diff --git a/drivers/clk/at91/clk-system.c b/drivers/clk/at91/clk-system.c index 021930e546..8be5c7f2b3 100644 --- a/drivers/clk/at91/clk-system.c +++ b/drivers/clk/at91/clk-system.c @@ -9,7 +9,6 @@ */ #include <common.h> #include <clock.h> -#include <of.h> #include <io.h> #include <linux/list.h> #include <linux/clk.h> @@ -91,7 +90,7 @@ static const struct clk_ops system_ops = { .is_enabled = clk_system_is_enabled, }; -static struct clk * +struct clk * at91_clk_register_system(struct regmap *regmap, const char *name, const char *parent_name, u8 id) { @@ -119,42 +118,3 @@ at91_clk_register_system(struct regmap *regmap, const char *name, return &sys->clk; } - -static int of_at91rm9200_clk_sys_setup(struct device_node *np) -{ - int num; - u32 id; - struct clk *clk; - const char *name; - struct device_node *sysclknp; - const char *parent_name; - struct regmap *regmap; - - num = of_get_child_count(np); - if (num > (SYSTEM_MAX_ID + 1)) - return -EINVAL; - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - for_each_child_of_node(np, sysclknp) { - if (of_property_read_u32(sysclknp, "reg", &id)) - continue; - - if (of_property_read_string(np, "clock-output-names", &name)) - name = sysclknp->name; - - parent_name = of_clk_get_parent_name(sysclknp, 0); - - clk = at91_clk_register_system(regmap, name, parent_name, id); - if (IS_ERR(clk)) - continue; - - of_clk_add_provider(sysclknp, of_clk_src_simple_get, clk); - } - - return 0; -} -CLK_OF_DECLARE(at91rm9200_clk_sys, "atmel,at91rm9200-clk-system", - of_at91rm9200_clk_sys_setup); diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c index 99ba671c98..0eb0b1f5bc 100644 --- a/drivers/clk/at91/clk-usb.c +++ b/drivers/clk/at91/clk-usb.c @@ -10,7 +10,6 @@ #include <common.h> #include <clock.h> -#include <of.h> #include <io.h> #include <linux/list.h> #include <linux/clk.h> @@ -144,7 +143,7 @@ static const struct clk_ops at91sam9n12_usb_ops = { .set_rate = at91sam9x5_clk_usb_set_rate, }; -static struct clk * +struct clk * at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name, const char **parent_names, u8 num_parents) { @@ -172,7 +171,7 @@ at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name, return &usb->clk; } -static struct clk * +struct clk * at91sam9n12_clk_register_usb(struct regmap *regmap, const char *name, const char *parent_name) { @@ -282,7 +281,7 @@ static const struct clk_ops at91rm9200_usb_ops = { .set_rate = at91rm9200_clk_usb_set_rate, }; -static struct clk * +struct clk * at91rm9200_clk_register_usb(struct regmap *regmap, const char *name, const char *parent_name, const u32 *divisors) { @@ -308,90 +307,3 @@ at91rm9200_clk_register_usb(struct regmap *regmap, const char *name, return &usb->clk; } - -static int of_at91sam9x5_clk_usb_setup(struct device_node *np) -{ - struct clk *clk; - unsigned int num_parents; - const char *parent_names[USB_SOURCE_MAX]; - const char *name = np->name; - struct regmap *regmap; - - num_parents = of_clk_get_parent_count(np); - if (num_parents == 0 || num_parents > USB_SOURCE_MAX) - return -EINVAL; - - of_clk_parent_fill(np, parent_names, num_parents); - - of_property_read_string(np, "clock-output-names", &name); - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - clk = at91sam9x5_clk_register_usb(regmap, name, parent_names, - num_parents); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} -CLK_OF_DECLARE(at91sam9x5_clk_usb, "atmel,at91sam9x5-clk-usb", - of_at91sam9x5_clk_usb_setup); - -static int of_at91sam9n12_clk_usb_setup(struct device_node *np) -{ - struct clk *clk; - const char *parent_name; - const char *name = np->name; - struct regmap *regmap; - - parent_name = of_clk_get_parent_name(np, 0); - if (!parent_name) - return -EINVAL; - - of_property_read_string(np, "clock-output-names", &name); - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - clk = at91sam9n12_clk_register_usb(regmap, name, parent_name); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} -CLK_OF_DECLARE(at91sam9n12_clk_usb, "atmel,at91sam9n12-clk-usb", - of_at91sam9n12_clk_usb_setup); - -static int of_at91rm9200_clk_usb_setup(struct device_node *np) -{ - struct clk *clk; - const char *parent_name; - const char *name = np->name; - u32 divisors[4] = {0, 0, 0, 0}; - struct regmap *regmap; - - parent_name = of_clk_get_parent_name(np, 0); - if (!parent_name) - return -EINVAL; - - of_property_read_u32_array(np, "atmel,clk-divisors", divisors, 4); - if (!divisors[0]) - return -EINVAL; - - of_property_read_string(np, "clock-output-names", &name); - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - clk = at91rm9200_clk_register_usb(regmap, name, parent_name, divisors); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - return of_clk_add_provider(np, of_clk_src_simple_get, clk); -} -CLK_OF_DECLARE(at91rm9200_clk_usb, "atmel,at91rm9200-clk-usb", - of_at91rm9200_clk_usb_setup); diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c index 6a1c5e6df3..c40af34d0d 100644 --- a/drivers/clk/at91/clk-utmi.c +++ b/drivers/clk/at91/clk-utmi.c @@ -10,21 +10,27 @@ #include <common.h> #include <clock.h> -#include <of.h> #include <linux/list.h> #include <linux/clk.h> #include <linux/clk/at91_pmc.h> #include <mfd/syscon.h> #include <regmap.h> +#include <soc/at91/atmel-sfr.h> + #include "pmc.h" -#define UTMI_FIXED_MUL 40 +/* + * The purpose of this clock is to generate a 480 MHz signal. A different + * rate can't be configured. + */ +#define UTMI_RATE 480000000 struct clk_utmi { struct clk clk; - struct regmap *regmap; const char *parent; + struct regmap *regmap_pmc; + struct regmap *regmap_sfr; }; #define to_clk_utmi(clk) container_of(clk, struct clk_utmi, clk) @@ -40,13 +46,55 @@ static inline bool clk_utmi_ready(struct regmap *regmap) static int clk_utmi_enable(struct clk *clk) { + struct clk *hw_parent; struct clk_utmi *utmi = to_clk_utmi(clk); unsigned int uckr = AT91_PMC_UPLLEN | AT91_PMC_UPLLCOUNT | AT91_PMC_BIASEN; + unsigned int utmi_ref_clk_freq; + unsigned long parent_rate; + + /* + * If mainck rate is different from 12 MHz, we have to configure the + * FREQ field of the SFR_UTMICKTRIM register to generate properly + * the utmi clock. + */ + hw_parent = clk_get_parent(clk); + parent_rate = clk_get_rate(hw_parent); + + switch (parent_rate) { + case 12000000: + utmi_ref_clk_freq = 0; + break; + case 16000000: + utmi_ref_clk_freq = 1; + break; + case 24000000: + utmi_ref_clk_freq = 2; + break; + /* + * Not supported on SAMA5D2 but it's not an issue since MAINCK + * maximum value is 24 MHz. + */ + case 48000000: + utmi_ref_clk_freq = 3; + break; + default: + pr_err("UTMICK: unsupported mainck rate\n"); + return -EINVAL; + } + + + if (utmi->regmap_sfr) { + regmap_write_bits(utmi->regmap_sfr, AT91_SFR_UTMICKTRIM, + AT91_UTMICKTRIM_FREQ, utmi_ref_clk_freq); + } else if (utmi_ref_clk_freq) { + pr_err("UTMICK: sfr node required\n"); + return -EINVAL; + } + regmap_write_bits(utmi->regmap_pmc, AT91_CKGR_UCKR, uckr, uckr); - regmap_write_bits(utmi->regmap, AT91_CKGR_UCKR, uckr, uckr); - while (!clk_utmi_ready(utmi->regmap)) + while (!clk_utmi_ready(utmi->regmap_pmc)) barrier(); return 0; @@ -56,21 +104,22 @@ static int clk_utmi_is_enabled(struct clk *clk) { struct clk_utmi *utmi = to_clk_utmi(clk); - return clk_utmi_ready(utmi->regmap); + return clk_utmi_ready(utmi->regmap_pmc); } static void clk_utmi_disable(struct clk *clk) { struct clk_utmi *utmi = to_clk_utmi(clk); - regmap_write_bits(utmi->regmap, AT91_CKGR_UCKR, AT91_PMC_UPLLEN, 0); + regmap_write_bits(utmi->regmap_pmc, AT91_CKGR_UCKR, + AT91_PMC_UPLLEN, 0); } static unsigned long clk_utmi_recalc_rate(struct clk *clk, unsigned long parent_rate) { - /* UTMI clk is a fixed clk multiplier */ - return parent_rate * UTMI_FIXED_MUL; + /* UTMI clk rate is fixed */ + return UTMI_RATE; } static const struct clk_ops utmi_ops = { @@ -80,8 +129,8 @@ static const struct clk_ops utmi_ops = { .recalc_rate = clk_utmi_recalc_rate, }; -static struct clk * __init -at91_clk_register_utmi(struct regmap *regmap, +struct clk * __init +at91_clk_register_utmi(struct regmap *regmap_pmc, struct regmap *regmap_sfr, const char *name, const char *parent_name) { int ret; @@ -100,7 +149,8 @@ at91_clk_register_utmi(struct regmap *regmap, /* utmi->clk.flags = CLK_SET_RATE_GATE; */ - utmi->regmap = regmap; + utmi->regmap_pmc = regmap_pmc; + utmi->regmap_sfr = regmap_sfr; ret = clk_register(&utmi->clk); if (ret) { @@ -110,29 +160,3 @@ at91_clk_register_utmi(struct regmap *regmap, return &utmi->clk; } -#if defined(CONFIG_OFTREE) && defined(CONFIG_COMMON_CLK_OF_PROVIDER) -static void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np) -{ - struct clk *clk; - const char *parent_name; - const char *name = np->name; - struct regmap *regmap; - - parent_name = of_clk_get_parent_name(np, 0); - - of_property_read_string(np, "clock-output-names", &name); - - regmap = syscon_node_to_regmap(of_get_parent(np)); - if (IS_ERR(regmap)) - return; - - clk = at91_clk_register_utmi(regmap, name, parent_name); - if (IS_ERR(clk)) - return; - - of_clk_add_provider(np, of_clk_src_simple_get, clk); - return; -} -CLK_OF_DECLARE(at91sam9x5_clk_utmi, "atmel,at91sam9x5-clk-utmi", - of_at91sam9x5_clk_utmi_setup); -#endif diff --git a/drivers/clk/at91/dt-compat.c b/drivers/clk/at91/dt-compat.c new file mode 100644 index 0000000000..bbd670641b --- /dev/null +++ b/drivers/clk/at91/dt-compat.c @@ -0,0 +1,961 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/kernel.h> +#include <linux/clk.h> +#include <of.h> +#include <driver.h> +#include <regmap.h> +#include <mfd/syscon.h> + + +#include "pmc.h" + +#define MASTER_SOURCE_MAX 4 + +#define PERIPHERAL_AT91RM9200 0 +#define PERIPHERAL_AT91SAM9X5 1 + +#define PERIPHERAL_MAX 64 + +#define PERIPHERAL_ID_MIN 2 + +#define PROG_SOURCE_MAX 5 +#define PROG_ID_MAX 7 + +#define SYSTEM_MAX_ID 31 + +#ifdef CONFIG_HAVE_AT91_AUDIO_PLL +static void __init of_sama5d2_clk_audio_pll_frac_setup(struct device_node *np) +{ + struct clk *hw; + const char *name = np->name; + const char *parent_name; + struct regmap *regmap; + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + parent_name = of_clk_get_parent_name(np, 0); + + hw = at91_clk_register_audio_pll_frac(regmap, name, parent_name); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(of_sama5d2_clk_audio_pll_frac_setup, + "atmel,sama5d2-clk-audio-pll-frac", + of_sama5d2_clk_audio_pll_frac_setup); + +static void __init of_sama5d2_clk_audio_pll_pad_setup(struct device_node *np) +{ + struct clk *hw; + const char *name = np->name; + const char *parent_name; + struct regmap *regmap; + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + parent_name = of_clk_get_parent_name(np, 0); + + hw = at91_clk_register_audio_pll_pad(regmap, name, parent_name); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(of_sama5d2_clk_audio_pll_pad_setup, + "atmel,sama5d2-clk-audio-pll-pad", + of_sama5d2_clk_audio_pll_pad_setup); + +static void __init of_sama5d2_clk_audio_pll_pmc_setup(struct device_node *np) +{ + struct clk *hw; + const char *name = np->name; + const char *parent_name; + struct regmap *regmap; + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + parent_name = of_clk_get_parent_name(np, 0); + + hw = at91_clk_register_audio_pll_pmc(regmap, name, parent_name); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(of_sama5d2_clk_audio_pll_pmc_setup, + "atmel,sama5d2-clk-audio-pll-pmc", + of_sama5d2_clk_audio_pll_pmc_setup); +#endif /* CONFIG_HAVE_AT91_AUDIO_PLL */ + +#ifdef CONFIG_HAVE_AT91_GENERATED_CLK +#define GENERATED_SOURCE_MAX 6 + +#define GCK_ID_I2S0 54 +#define GCK_ID_I2S1 55 +#define GCK_ID_CLASSD 59 + +static void __init of_sama5d2_clk_generated_setup(struct device_node *np) +{ + int num; + u32 id; + const char *name; + struct clk *hw; + unsigned int num_parents; + const char *parent_names[GENERATED_SOURCE_MAX]; + struct device_node *gcknp; + struct clk_range range = CLK_RANGE(0, 0); + struct regmap *regmap; + + num_parents = of_clk_get_parent_count(np); + if (num_parents == 0 || num_parents > GENERATED_SOURCE_MAX) + return; + + of_clk_parent_fill(np, parent_names, num_parents); + + num = of_get_child_count(np); + if (!num || num > PERIPHERAL_MAX) + return; + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + for_each_child_of_node(np, gcknp) { + bool pll_audio = false; + + if (of_property_read_u32(gcknp, "reg", &id)) + continue; + + if (id < PERIPHERAL_ID_MIN || id >= PERIPHERAL_MAX) + continue; + + if (of_property_read_string(np, "clock-output-names", &name)) + name = gcknp->name; + + of_at91_get_clk_range(gcknp, "atmel,clk-output-range", + &range); + + if (of_device_is_compatible(np, "atmel,sama5d2-clk-generated") && + (id == GCK_ID_I2S0 || id == GCK_ID_I2S1 || + id == GCK_ID_CLASSD)) + pll_audio = true; + + hw = at91_clk_register_generated(regmap, name, + parent_names, num_parents, + id, pll_audio, &range); + if (IS_ERR(hw)) + continue; + + of_clk_add_provider(gcknp, of_clk_src_simple_get, hw); + } +} +CLK_OF_DECLARE(of_sama5d2_clk_generated_setup, "atmel,sama5d2-clk-generated", + of_sama5d2_clk_generated_setup); +#endif /* CONFIG_HAVE_AT91_GENERATED_CLK */ + +#ifdef CONFIG_HAVE_AT91_H32MX +static void __init of_sama5d4_clk_h32mx_setup(struct device_node *np) +{ + struct clk *hw; + const char *name = np->name; + const char *parent_name; + struct regmap *regmap; + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + parent_name = of_clk_get_parent_name(np, 0); + + hw = at91_clk_register_h32mx(regmap, name, parent_name); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(of_sama5d4_clk_h32mx_setup, "atmel,sama5d4-clk-h32mx", + of_sama5d4_clk_h32mx_setup); +#endif /* CONFIG_HAVE_AT91_H32MX */ + +#ifdef CONFIG_HAVE_AT91_I2S_MUX_CLK +#define I2S_BUS_NR 2 + +static void __init of_sama5d2_clk_i2s_mux_setup(struct device_node *np) +{ + struct regmap *regmap_sfr; + u8 bus_id; + const char *parent_names[2]; + struct device_node *i2s_mux_np; + struct clk *hw; + int ret; + + regmap_sfr = syscon_regmap_lookup_by_compatible("atmel,sama5d2-sfr"); + if (IS_ERR(regmap_sfr)) + return; + + for_each_child_of_node(np, i2s_mux_np) { + if (of_property_read_u8(i2s_mux_np, "reg", &bus_id)) + continue; + + if (bus_id > I2S_BUS_NR) + continue; + + ret = of_clk_parent_fill(i2s_mux_np, parent_names, 2); + if (ret != 2) + continue; + + hw = at91_clk_i2s_mux_register(regmap_sfr, i2s_mux_np->name, + parent_names, 2, bus_id); + if (IS_ERR(hw)) + continue; + + of_clk_add_provider(i2s_mux_np, of_clk_src_simple_get, hw); + } +} +CLK_OF_DECLARE(sama5d2_clk_i2s_mux, "atmel,sama5d2-clk-i2s-mux", + of_sama5d2_clk_i2s_mux_setup); +#endif /* CONFIG_HAVE_AT91_I2S_MUX_CLK */ + +static void __init of_at91rm9200_clk_main_osc_setup(struct device_node *np) +{ + struct clk *hw; + const char *name = np->name; + const char *parent_name; + struct regmap *regmap; + bool bypass; + + of_property_read_string(np, "clock-output-names", &name); + bypass = of_property_read_bool(np, "atmel,osc-bypass"); + parent_name = of_clk_get_parent_name(np, 0); + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + hw = at91_clk_register_main_osc(regmap, name, parent_name, bypass); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91rm9200_clk_main_osc, "atmel,at91rm9200-clk-main-osc", + of_at91rm9200_clk_main_osc_setup); + +static void __init of_at91sam9x5_clk_main_rc_osc_setup(struct device_node *np) +{ + struct clk *hw; + u32 frequency = 0; + u32 accuracy = 0; + const char *name = np->name; + struct regmap *regmap; + + of_property_read_string(np, "clock-output-names", &name); + of_property_read_u32(np, "clock-frequency", &frequency); + of_property_read_u32(np, "clock-accuracy", &accuracy); + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + hw = at91_clk_register_main_rc_osc(regmap, name, frequency, accuracy); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91sam9x5_clk_main_rc_osc, "atmel,at91sam9x5-clk-main-rc-osc", + of_at91sam9x5_clk_main_rc_osc_setup); + +static void __init of_at91rm9200_clk_main_setup(struct device_node *np) +{ + struct clk *hw; + const char *parent_name; + const char *name = np->name; + struct regmap *regmap; + + parent_name = of_clk_get_parent_name(np, 0); + of_property_read_string(np, "clock-output-names", &name); + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + hw = at91_clk_register_rm9200_main(regmap, name, parent_name); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91rm9200_clk_main, "atmel,at91rm9200-clk-main", + of_at91rm9200_clk_main_setup); + +static void __init of_at91sam9x5_clk_main_setup(struct device_node *np) +{ + struct clk *hw; + const char *parent_names[2]; + unsigned int num_parents; + const char *name = np->name; + struct regmap *regmap; + + num_parents = of_clk_get_parent_count(np); + if (num_parents == 0 || num_parents > 2) + return; + + of_clk_parent_fill(np, parent_names, num_parents); + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + of_property_read_string(np, "clock-output-names", &name); + + hw = at91_clk_register_sam9x5_main(regmap, name, parent_names, + num_parents); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91sam9x5_clk_main, "atmel,at91sam9x5-clk-main", + of_at91sam9x5_clk_main_setup); + +static struct clk_master_characteristics * __init +of_at91_clk_master_get_characteristics(struct device_node *np) +{ + struct clk_master_characteristics *characteristics; + + characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL); + if (!characteristics) + return NULL; + + if (of_at91_get_clk_range(np, "atmel,clk-output-range", &characteristics->output)) + goto out_free_characteristics; + + of_property_read_u32_array(np, "atmel,clk-divisors", + characteristics->divisors, 4); + + characteristics->have_div3_pres = + of_property_read_bool(np, "atmel,master-clk-have-div3-pres"); + + return characteristics; + +out_free_characteristics: + kfree(characteristics); + return NULL; +} + +static void __init +of_at91_clk_master_setup(struct device_node *np, + const struct clk_master_layout *layout) +{ + struct clk *hw; + unsigned int num_parents; + const char *parent_names[MASTER_SOURCE_MAX]; + const char *name = np->name; + struct clk_master_characteristics *characteristics; + struct regmap *regmap; + + num_parents = of_clk_get_parent_count(np); + if (num_parents == 0 || num_parents > MASTER_SOURCE_MAX) + return; + + of_clk_parent_fill(np, parent_names, num_parents); + + of_property_read_string(np, "clock-output-names", &name); + + characteristics = of_at91_clk_master_get_characteristics(np); + if (!characteristics) + return; + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + hw = at91_clk_register_master(regmap, name, num_parents, + parent_names, layout, + characteristics); + if (IS_ERR(hw)) + goto out_free_characteristics; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); + return; + +out_free_characteristics: + kfree(characteristics); +} + +static void __init of_at91rm9200_clk_master_setup(struct device_node *np) +{ + of_at91_clk_master_setup(np, &at91rm9200_master_layout); +} +CLK_OF_DECLARE(at91rm9200_clk_master, "atmel,at91rm9200-clk-master", + of_at91rm9200_clk_master_setup); + +static void __init of_at91sam9x5_clk_master_setup(struct device_node *np) +{ + of_at91_clk_master_setup(np, &at91sam9x5_master_layout); +} +CLK_OF_DECLARE(at91sam9x5_clk_master, "atmel,at91sam9x5-clk-master", + of_at91sam9x5_clk_master_setup); + +static void __init +of_at91_clk_periph_setup(struct device_node *np, u8 type) +{ + int num; + u32 id; + struct clk *hw; + const char *parent_name; + const char *name; + struct device_node *periphclknp; + struct regmap *regmap; + + parent_name = of_clk_get_parent_name(np, 0); + if (!parent_name) + return; + + num = of_get_child_count(np); + if (!num || num > PERIPHERAL_MAX) + return; + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + for_each_child_of_node(np, periphclknp) { + if (of_property_read_u32(periphclknp, "reg", &id)) + continue; + + if (id >= PERIPHERAL_MAX) + continue; + + if (of_property_read_string(np, "clock-output-names", &name)) + name = periphclknp->name; + + if (type == PERIPHERAL_AT91RM9200) { + hw = at91_clk_register_peripheral(regmap, name, + parent_name, id); + } else { + struct clk_range range = CLK_RANGE(0, 0); + + of_at91_get_clk_range(periphclknp, + "atmel,clk-output-range", + &range); + + hw = at91_clk_register_sam9x5_peripheral(regmap, + name, + parent_name, + id, &range); + } + + if (IS_ERR(hw)) + continue; + + of_clk_add_provider(periphclknp, of_clk_src_simple_get, hw); + } +} + +static void __init of_at91rm9200_clk_periph_setup(struct device_node *np) +{ + of_at91_clk_periph_setup(np, PERIPHERAL_AT91RM9200); +} +CLK_OF_DECLARE(at91rm9200_clk_periph, "atmel,at91rm9200-clk-peripheral", + of_at91rm9200_clk_periph_setup); + +static void __init of_at91sam9x5_clk_periph_setup(struct device_node *np) +{ + of_at91_clk_periph_setup(np, PERIPHERAL_AT91SAM9X5); +} +CLK_OF_DECLARE(at91sam9x5_clk_periph, "atmel,at91sam9x5-clk-peripheral", + of_at91sam9x5_clk_periph_setup); + +static struct clk_pll_characteristics * __init +of_at91_clk_pll_get_characteristics(struct device_node *np) +{ + int i; + int offset; + u32 tmp; + int num_output; + u32 num_cells; + struct clk_range input; + struct clk_range *output; + u8 *out = NULL; + u16 *icpll = NULL; + struct clk_pll_characteristics *characteristics; + + if (of_at91_get_clk_range(np, "atmel,clk-input-range", &input)) + return NULL; + + if (of_property_read_u32(np, "#atmel,pll-clk-output-range-cells", + &num_cells)) + return NULL; + + if (num_cells < 2 || num_cells > 4) + return NULL; + + if (!of_get_property(np, "atmel,pll-clk-output-ranges", &tmp)) + return NULL; + num_output = tmp / (sizeof(u32) * num_cells); + + characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL); + if (!characteristics) + return NULL; + + output = kcalloc(num_output, sizeof(*output), GFP_KERNEL); + if (!output) + goto out_free_characteristics; + + if (num_cells > 2) { + out = kcalloc(num_output, sizeof(*out), GFP_KERNEL); + if (!out) + goto out_free_output; + } + + if (num_cells > 3) { + icpll = kcalloc(num_output, sizeof(*icpll), GFP_KERNEL); + if (!icpll) + goto out_free_output; + } + + for (i = 0; i < num_output; i++) { + offset = i * num_cells; + if (of_property_read_u32_index(np, + "atmel,pll-clk-output-ranges", + offset, &tmp)) + goto out_free_output; + output[i].min = tmp; + if (of_property_read_u32_index(np, + "atmel,pll-clk-output-ranges", + offset + 1, &tmp)) + goto out_free_output; + output[i].max = tmp; + + if (num_cells == 2) + continue; + + if (of_property_read_u32_index(np, + "atmel,pll-clk-output-ranges", + offset + 2, &tmp)) + goto out_free_output; + out[i] = tmp; + + if (num_cells == 3) + continue; + + if (of_property_read_u32_index(np, + "atmel,pll-clk-output-ranges", + offset + 3, &tmp)) + goto out_free_output; + icpll[i] = tmp; + } + + characteristics->input = input; + characteristics->num_output = num_output; + characteristics->output = output; + characteristics->out = out; + characteristics->icpll = icpll; + return characteristics; + +out_free_output: + kfree(icpll); + kfree(out); + kfree(output); +out_free_characteristics: + kfree(characteristics); + return NULL; +} + +static void __init +of_at91_clk_pll_setup(struct device_node *np, + const struct clk_pll_layout *layout) +{ + u32 id; + struct clk *hw; + struct regmap *regmap; + const char *parent_name; + const char *name = np->name; + struct clk_pll_characteristics *characteristics; + + if (of_property_read_u32(np, "reg", &id)) + return; + + parent_name = of_clk_get_parent_name(np, 0); + + of_property_read_string(np, "clock-output-names", &name); + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + characteristics = of_at91_clk_pll_get_characteristics(np); + if (!characteristics) + return; + + hw = at91_clk_register_pll(regmap, name, parent_name, id, layout, + characteristics); + if (IS_ERR(hw)) + goto out_free_characteristics; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); + return; + +out_free_characteristics: + kfree(characteristics); +} + +static void __init of_at91rm9200_clk_pll_setup(struct device_node *np) +{ + of_at91_clk_pll_setup(np, &at91rm9200_pll_layout); +} +CLK_OF_DECLARE(at91rm9200_clk_pll, "atmel,at91rm9200-clk-pll", + of_at91rm9200_clk_pll_setup); + +static void __init of_at91sam9g45_clk_pll_setup(struct device_node *np) +{ + of_at91_clk_pll_setup(np, &at91sam9g45_pll_layout); +} +CLK_OF_DECLARE(at91sam9g45_clk_pll, "atmel,at91sam9g45-clk-pll", + of_at91sam9g45_clk_pll_setup); + +static void __init of_at91sam9g20_clk_pllb_setup(struct device_node *np) +{ + of_at91_clk_pll_setup(np, &at91sam9g20_pllb_layout); +} +CLK_OF_DECLARE(at91sam9g20_clk_pllb, "atmel,at91sam9g20-clk-pllb", + of_at91sam9g20_clk_pllb_setup); + +static void __init of_sama5d3_clk_pll_setup(struct device_node *np) +{ + of_at91_clk_pll_setup(np, &sama5d3_pll_layout); +} +CLK_OF_DECLARE(sama5d3_clk_pll, "atmel,sama5d3-clk-pll", + of_sama5d3_clk_pll_setup); + +static void __init +of_at91sam9x5_clk_plldiv_setup(struct device_node *np) +{ + struct clk *hw; + const char *parent_name; + const char *name = np->name; + struct regmap *regmap; + + parent_name = of_clk_get_parent_name(np, 0); + + of_property_read_string(np, "clock-output-names", &name); + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + hw = at91_clk_register_plldiv(regmap, name, parent_name); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91sam9x5_clk_plldiv, "atmel,at91sam9x5-clk-plldiv", + of_at91sam9x5_clk_plldiv_setup); + +static void __init +of_at91_clk_prog_setup(struct device_node *np, + const struct clk_programmable_layout *layout) +{ + int num; + u32 id; + struct clk *hw; + unsigned int num_parents; + const char *parent_names[PROG_SOURCE_MAX]; + const char *name; + struct device_node *progclknp; + struct regmap *regmap; + + num_parents = of_clk_get_parent_count(np); + if (num_parents == 0 || num_parents > PROG_SOURCE_MAX) + return; + + of_clk_parent_fill(np, parent_names, num_parents); + + num = of_get_child_count(np); + if (!num || num > (PROG_ID_MAX + 1)) + return; + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + for_each_child_of_node(np, progclknp) { + if (of_property_read_u32(progclknp, "reg", &id)) + continue; + + if (of_property_read_string(np, "clock-output-names", &name)) + name = progclknp->name; + + hw = at91_clk_register_programmable(regmap, name, + parent_names, num_parents, + id, layout); + if (IS_ERR(hw)) + continue; + + of_clk_add_provider(progclknp, of_clk_src_simple_get, hw); + } +} + +static void __init of_at91rm9200_clk_prog_setup(struct device_node *np) +{ + of_at91_clk_prog_setup(np, &at91rm9200_programmable_layout); +} +CLK_OF_DECLARE(at91rm9200_clk_prog, "atmel,at91rm9200-clk-programmable", + of_at91rm9200_clk_prog_setup); + +static void __init of_at91sam9g45_clk_prog_setup(struct device_node *np) +{ + of_at91_clk_prog_setup(np, &at91sam9g45_programmable_layout); +} +CLK_OF_DECLARE(at91sam9g45_clk_prog, "atmel,at91sam9g45-clk-programmable", + of_at91sam9g45_clk_prog_setup); + +static void __init of_at91sam9x5_clk_prog_setup(struct device_node *np) +{ + of_at91_clk_prog_setup(np, &at91sam9x5_programmable_layout); +} +CLK_OF_DECLARE(at91sam9x5_clk_prog, "atmel,at91sam9x5-clk-programmable", + of_at91sam9x5_clk_prog_setup); + +static void __init of_at91sam9260_clk_slow_setup(struct device_node *np) +{ + struct clk *hw; + const char *parent_names[2]; + unsigned int num_parents; + const char *name = np->name; + struct regmap *regmap; + + num_parents = of_clk_get_parent_count(np); + if (num_parents != 2) + return; + + of_clk_parent_fill(np, parent_names, num_parents); + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + of_property_read_string(np, "clock-output-names", &name); + + hw = at91_clk_register_sam9260_slow(regmap, name, parent_names, + num_parents); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91sam9260_clk_slow, "atmel,at91sam9260-clk-slow", + of_at91sam9260_clk_slow_setup); + +#ifdef CONFIG_HAVE_AT91_SMD +#define SMD_SOURCE_MAX 2 + +static void __init of_at91sam9x5_clk_smd_setup(struct device_node *np) +{ + struct clk *hw; + unsigned int num_parents; + const char *parent_names[SMD_SOURCE_MAX]; + const char *name = np->name; + struct regmap *regmap; + + num_parents = of_clk_get_parent_count(np); + if (num_parents == 0 || num_parents > SMD_SOURCE_MAX) + return; + + of_clk_parent_fill(np, parent_names, num_parents); + + of_property_read_string(np, "clock-output-names", &name); + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + hw = at91sam9x5_clk_register_smd(regmap, name, parent_names, + num_parents); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91sam9x5_clk_smd, "atmel,at91sam9x5-clk-smd", + of_at91sam9x5_clk_smd_setup); +#endif /* CONFIG_HAVE_AT91_SMD */ + +static void __init of_at91rm9200_clk_sys_setup(struct device_node *np) +{ + int num; + u32 id; + struct clk *hw; + const char *name; + struct device_node *sysclknp; + const char *parent_name; + struct regmap *regmap; + + num = of_get_child_count(np); + if (num > (SYSTEM_MAX_ID + 1)) + return; + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + for_each_child_of_node(np, sysclknp) { + if (of_property_read_u32(sysclknp, "reg", &id)) + continue; + + if (of_property_read_string(np, "clock-output-names", &name)) + name = sysclknp->name; + + parent_name = of_clk_get_parent_name(sysclknp, 0); + + hw = at91_clk_register_system(regmap, name, parent_name, id); + if (IS_ERR(hw)) + continue; + + of_clk_add_provider(sysclknp, of_clk_src_simple_get, hw); + } +} +CLK_OF_DECLARE(at91rm9200_clk_sys, "atmel,at91rm9200-clk-system", + of_at91rm9200_clk_sys_setup); + +#ifdef CONFIG_HAVE_AT91_USB_CLK +#define USB_SOURCE_MAX 2 + +static void __init of_at91sam9x5_clk_usb_setup(struct device_node *np) +{ + struct clk *hw; + unsigned int num_parents; + const char *parent_names[USB_SOURCE_MAX]; + const char *name = np->name; + struct regmap *regmap; + + num_parents = of_clk_get_parent_count(np); + if (num_parents == 0 || num_parents > USB_SOURCE_MAX) + return; + + of_clk_parent_fill(np, parent_names, num_parents); + + of_property_read_string(np, "clock-output-names", &name); + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + hw = at91sam9x5_clk_register_usb(regmap, name, parent_names, + num_parents); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91sam9x5_clk_usb, "atmel,at91sam9x5-clk-usb", + of_at91sam9x5_clk_usb_setup); + +static void __init of_at91sam9n12_clk_usb_setup(struct device_node *np) +{ + struct clk *hw; + const char *parent_name; + const char *name = np->name; + struct regmap *regmap; + + parent_name = of_clk_get_parent_name(np, 0); + if (!parent_name) + return; + + of_property_read_string(np, "clock-output-names", &name); + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + + hw = at91sam9n12_clk_register_usb(regmap, name, parent_name); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91sam9n12_clk_usb, "atmel,at91sam9n12-clk-usb", + of_at91sam9n12_clk_usb_setup); + +static void __init of_at91rm9200_clk_usb_setup(struct device_node *np) +{ + struct clk *hw; + const char *parent_name; + const char *name = np->name; + u32 divisors[4] = {0, 0, 0, 0}; + struct regmap *regmap; + + parent_name = of_clk_get_parent_name(np, 0); + if (!parent_name) + return; + + of_property_read_u32_array(np, "atmel,clk-divisors", divisors, 4); + if (!divisors[0]) + return; + + of_property_read_string(np, "clock-output-names", &name); + + regmap = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap)) + return; + hw = at91rm9200_clk_register_usb(regmap, name, parent_name, divisors); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91rm9200_clk_usb, "atmel,at91rm9200-clk-usb", + of_at91rm9200_clk_usb_setup); +#endif /* CONFIG_HAVE_AT91_USB_CLK */ + +#ifdef CONFIG_HAVE_AT91_UTMI +static void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np) +{ + struct clk *hw; + const char *parent_name; + const char *name = np->name; + struct regmap *regmap_pmc, *regmap_sfr; + + parent_name = of_clk_get_parent_name(np, 0); + + of_property_read_string(np, "clock-output-names", &name); + + regmap_pmc = syscon_node_to_regmap(of_get_parent(np)); + if (IS_ERR(regmap_pmc)) + return; + + /* + * If the device supports different mainck rates, this value has to be + * set in the UTMI Clock Trimming register. + * - 9x5: mainck supports several rates but it is indicated that a + * 12 MHz is needed in case of USB. + * - sama5d3 and sama5d2: mainck supports several rates. Configuring + * the FREQ field of the UTMI Clock Trimming register is mandatory. + * - sama5d4: mainck is at 12 MHz. + * + * We only need to retrieve sama5d3 or sama5d2 sfr regmap. + */ + regmap_sfr = syscon_regmap_lookup_by_compatible("atmel,sama5d3-sfr"); + if (IS_ERR(regmap_sfr)) { + regmap_sfr = syscon_regmap_lookup_by_compatible("atmel,sama5d2-sfr"); + if (IS_ERR(regmap_sfr)) + regmap_sfr = NULL; + } + + hw = at91_clk_register_utmi(regmap_pmc, regmap_sfr, name, parent_name); + if (IS_ERR(hw)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, hw); +} +CLK_OF_DECLARE(at91sam9x5_clk_utmi, "atmel,at91sam9x5-clk-utmi", + of_at91sam9x5_clk_utmi_setup); +#endif /* CONFIG_HAVE_AT91_UTMI */ diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c index d156d50ca8..aa73d61c5e 100644 --- a/drivers/clk/at91/pmc.c +++ b/drivers/clk/at91/pmc.c @@ -15,8 +15,13 @@ #include <mfd/syscon.h> #include <regmap.h> +#include <dt-bindings/clock/at91.h> + #include "pmc.h" +#define PMC_MAX_IDS 128 +#define PMC_MAX_PCKS 8 + int of_at91_get_clk_range(struct device_node *np, const char *propname, struct clk_range *range) { @@ -39,3 +44,246 @@ int of_at91_get_clk_range(struct device_node *np, const char *propname, return 0; } EXPORT_SYMBOL_GPL(of_at91_get_clk_range); + +struct clk *of_clk_hw_pmc_get(struct of_phandle_args *clkspec, void *data) +{ + unsigned int type = clkspec->args[0]; + unsigned int idx = clkspec->args[1]; + struct pmc_data *pmc_data = data; + + switch (type) { + case PMC_TYPE_CORE: + if (idx < pmc_data->ncore) + return pmc_data->chws[idx]; + break; + case PMC_TYPE_SYSTEM: + if (idx < pmc_data->nsystem) + return pmc_data->shws[idx]; + break; + case PMC_TYPE_PERIPHERAL: + if (idx < pmc_data->nperiph) + return pmc_data->phws[idx]; + break; + case PMC_TYPE_GCK: + if (idx < pmc_data->ngck) + return pmc_data->ghws[idx]; + break; + default: + break; + } + + pr_err("%s: invalid type (%u) or index (%u)\n", __func__, type, idx); + + return ERR_PTR(-EINVAL); +} + +void pmc_data_free(struct pmc_data *pmc_data) +{ + kfree(pmc_data->chws); + kfree(pmc_data->shws); + kfree(pmc_data->phws); + kfree(pmc_data->ghws); +} + +struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem, + unsigned int nperiph, unsigned int ngck) +{ + struct pmc_data *pmc_data = kzalloc(sizeof(*pmc_data), GFP_KERNEL); + + if (!pmc_data) + return NULL; + + pmc_data->ncore = ncore; + pmc_data->chws = kcalloc(ncore, sizeof(struct clk_hw *), GFP_KERNEL); + if (!pmc_data->chws) + goto err; + + pmc_data->nsystem = nsystem; + pmc_data->shws = kcalloc(nsystem, sizeof(struct clk_hw *), GFP_KERNEL); + if (!pmc_data->shws) + goto err; + + pmc_data->nperiph = nperiph; + pmc_data->phws = kcalloc(nperiph, sizeof(struct clk_hw *), GFP_KERNEL); + if (!pmc_data->phws) + goto err; + + pmc_data->ngck = ngck; + pmc_data->ghws = kcalloc(ngck, sizeof(struct clk_hw *), GFP_KERNEL); + if (!pmc_data->ghws) + goto err; + + return pmc_data; + +err: + pmc_data_free(pmc_data); + + return NULL; +} + +#ifdef CONFIG_PM +static struct regmap *pmcreg; + +static u8 registered_ids[PMC_MAX_IDS]; +static u8 registered_pcks[PMC_MAX_PCKS]; + +static struct +{ + u32 scsr; + u32 pcsr0; + u32 uckr; + u32 mor; + u32 mcfr; + u32 pllar; + u32 mckr; + u32 usb; + u32 imr; + u32 pcsr1; + u32 pcr[PMC_MAX_IDS]; + u32 audio_pll0; + u32 audio_pll1; + u32 pckr[PMC_MAX_PCKS]; +} pmc_cache; + +/* + * As Peripheral ID 0 is invalid on AT91 chips, the identifier is stored + * without alteration in the table, and 0 is for unused clocks. + */ +void pmc_register_id(u8 id) +{ + int i; + + for (i = 0; i < PMC_MAX_IDS; i++) { + if (registered_ids[i] == 0) { + registered_ids[i] = id; + break; + } + if (registered_ids[i] == id) + break; + } +} + +/* + * As Programmable Clock 0 is valid on AT91 chips, there is an offset + * of 1 between the stored value and the real clock ID. + */ +void pmc_register_pck(u8 pck) +{ + int i; + + for (i = 0; i < PMC_MAX_PCKS; i++) { + if (registered_pcks[i] == 0) { + registered_pcks[i] = pck + 1; + break; + } + if (registered_pcks[i] == (pck + 1)) + break; + } +} + +static int pmc_suspend(void) +{ + int i; + u8 num; + + regmap_read(pmcreg, AT91_PMC_SCSR, &pmc_cache.scsr); + regmap_read(pmcreg, AT91_PMC_PCSR, &pmc_cache.pcsr0); + regmap_read(pmcreg, AT91_CKGR_UCKR, &pmc_cache.uckr); + regmap_read(pmcreg, AT91_CKGR_MOR, &pmc_cache.mor); + regmap_read(pmcreg, AT91_CKGR_MCFR, &pmc_cache.mcfr); + regmap_read(pmcreg, AT91_CKGR_PLLAR, &pmc_cache.pllar); + regmap_read(pmcreg, AT91_PMC_MCKR, &pmc_cache.mckr); + regmap_read(pmcreg, AT91_PMC_USB, &pmc_cache.usb); + regmap_read(pmcreg, AT91_PMC_IMR, &pmc_cache.imr); + regmap_read(pmcreg, AT91_PMC_PCSR1, &pmc_cache.pcsr1); + + for (i = 0; registered_ids[i]; i++) { + regmap_write(pmcreg, AT91_PMC_PCR, + (registered_ids[i] & AT91_PMC_PCR_PID_MASK)); + regmap_read(pmcreg, AT91_PMC_PCR, + &pmc_cache.pcr[registered_ids[i]]); + } + for (i = 0; registered_pcks[i]; i++) { + num = registered_pcks[i] - 1; + regmap_read(pmcreg, AT91_PMC_PCKR(num), &pmc_cache.pckr[num]); + } + + return 0; +} + +static bool pmc_ready(unsigned int mask) +{ + unsigned int status; + + regmap_read(pmcreg, AT91_PMC_SR, &status); + + return ((status & mask) == mask) ? 1 : 0; +} + +static void pmc_resume(void) +{ + int i; + u8 num; + u32 tmp; + u32 mask = AT91_PMC_MCKRDY | AT91_PMC_LOCKA; + + regmap_read(pmcreg, AT91_PMC_MCKR, &tmp); + if (pmc_cache.mckr != tmp) + pr_warn("MCKR was not configured properly by the firmware\n"); + regmap_read(pmcreg, AT91_CKGR_PLLAR, &tmp); + if (pmc_cache.pllar != tmp) + pr_warn("PLLAR was not configured properly by the firmware\n"); + + regmap_write(pmcreg, AT91_PMC_SCER, pmc_cache.scsr); + regmap_write(pmcreg, AT91_PMC_PCER, pmc_cache.pcsr0); + regmap_write(pmcreg, AT91_CKGR_UCKR, pmc_cache.uckr); + regmap_write(pmcreg, AT91_CKGR_MOR, pmc_cache.mor); + regmap_write(pmcreg, AT91_CKGR_MCFR, pmc_cache.mcfr); + regmap_write(pmcreg, AT91_PMC_USB, pmc_cache.usb); + regmap_write(pmcreg, AT91_PMC_IMR, pmc_cache.imr); + regmap_write(pmcreg, AT91_PMC_PCER1, pmc_cache.pcsr1); + + for (i = 0; registered_ids[i]; i++) { + regmap_write(pmcreg, AT91_PMC_PCR, + pmc_cache.pcr[registered_ids[i]] | + AT91_PMC_PCR_CMD); + } + for (i = 0; registered_pcks[i]; i++) { + num = registered_pcks[i] - 1; + regmap_write(pmcreg, AT91_PMC_PCKR(num), pmc_cache.pckr[num]); + } + + if (pmc_cache.uckr & AT91_PMC_UPLLEN) + mask |= AT91_PMC_LOCKU; + + while (!pmc_ready(mask)) + cpu_relax(); +} + +static struct syscore_ops pmc_syscore_ops = { + .suspend = pmc_suspend, + .resume = pmc_resume, +}; + +static const struct of_device_id sama5d2_pmc_dt_ids[] = { + { .compatible = "atmel,sama5d2-pmc" }, + { /* sentinel */ } +}; + +static int __init pmc_register_ops(void) +{ + struct device_node *np; + + np = of_find_matching_node(NULL, sama5d2_pmc_dt_ids); + + pmcreg = syscon_node_to_regmap(np); + if (IS_ERR(pmcreg)) + return PTR_ERR(pmcreg); + + register_syscore_ops(&pmc_syscore_ops); + + return 0; +} +/* This has to happen before arch_initcall because of the tcb_clksrc driver */ +postcore_initcall(pmc_register_ops); +#endif diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h index c6c14a79a4..529498308f 100644 --- a/drivers/clk/at91/pmc.h +++ b/drivers/clk/at91/pmc.h @@ -13,6 +13,19 @@ #define __PMC_H_ #include <io.h> +#include <linux/spinlock.h> +#include <printk.h> + +struct pmc_data { + unsigned int ncore; + struct clk **chws; + unsigned int nsystem; + struct clk **shws; + unsigned int nperiph; + struct clk **phws; + unsigned int ngck; + struct clk **ghws; +}; struct clk_range { unsigned long min; @@ -21,7 +34,163 @@ struct clk_range { #define CLK_RANGE(MIN, MAX) {.min = MIN, .max = MAX,} +struct clk_master_layout { + u32 mask; + u8 pres_shift; +}; + +extern const struct clk_master_layout at91rm9200_master_layout; +extern const struct clk_master_layout at91sam9x5_master_layout; + +struct clk_master_characteristics { + struct clk_range output; + u32 divisors[4]; + u8 have_div3_pres; +}; + +struct clk_pll_layout { + u32 pllr_mask; + u16 mul_mask; + u8 mul_shift; +}; + +extern const struct clk_pll_layout at91rm9200_pll_layout; +extern const struct clk_pll_layout at91sam9g45_pll_layout; +extern const struct clk_pll_layout at91sam9g20_pllb_layout; +extern const struct clk_pll_layout sama5d3_pll_layout; + +struct clk_pll_characteristics { + struct clk_range input; + int num_output; + struct clk_range *output; + u16 *icpll; + u8 *out; +}; + +struct clk_programmable_layout { + u8 pres_shift; + u8 css_mask; + u8 have_slck_mck; +}; + +extern const struct clk_programmable_layout at91rm9200_programmable_layout; +extern const struct clk_programmable_layout at91sam9g45_programmable_layout; +extern const struct clk_programmable_layout at91sam9x5_programmable_layout; + +#define ndck(a, s) (a[s - 1].id + 1) +#define nck(a) (a[ARRAY_SIZE(a) - 1].id + 1) +struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem, + unsigned int nperiph, unsigned int ngck); +void pmc_data_free(struct pmc_data *pmc_data); + int of_at91_get_clk_range(struct device_node *np, const char *propname, struct clk_range *range); +struct clk *of_clk_hw_pmc_get(struct of_phandle_args *clkspec, void *data); + +struct clk * +at91_clk_register_audio_pll_frac(struct regmap *regmap, const char *name, + const char *parent_name); + +struct clk * +at91_clk_register_audio_pll_pad(struct regmap *regmap, const char *name, + const char *parent_name); + +struct clk * +at91_clk_register_audio_pll_pmc(struct regmap *regmap, const char *name, + const char *parent_name); + +struct clk * +at91_clk_register_generated(struct regmap *regmap, + const char *name, const char **parent_names, + u8 num_parents, u8 id, bool pll_audio, + const struct clk_range *range); + +struct clk * +at91_clk_register_h32mx(struct regmap *regmap, const char *name, + const char *parent_name); + +struct clk * +at91_clk_i2s_mux_register(struct regmap *regmap, const char *name, + const char * const *parent_names, + unsigned int num_parents, u8 bus_id); + +struct clk * +at91_clk_register_main_rc_osc(struct regmap *regmap, const char *name, + u32 frequency, u32 accuracy); +struct clk * +at91_clk_register_main_osc(struct regmap *regmap, const char *name, + const char *parent_name, bool bypass); +struct clk * +at91_clk_register_rm9200_main(struct regmap *regmap, + const char *name, + const char *parent_name); +struct clk * +at91_clk_register_sam9x5_main(struct regmap *regmap, const char *name, + const char **parent_names, int num_parents); + +struct clk * +at91_clk_register_master(struct regmap *regmap, const char *name, + int num_parents, const char **parent_names, + const struct clk_master_layout *layout, + const struct clk_master_characteristics *characteristics); + +struct clk * +at91_clk_register_peripheral(struct regmap *regmap, const char *name, + const char *parent_name, u32 id); +struct clk * +at91_clk_register_sam9x5_peripheral(struct regmap *regmap, + const char *name, const char *parent_name, + u32 id, const struct clk_range *range); + +struct clk * +at91_clk_register_pll(struct regmap *regmap, const char *name, + const char *parent_name, u8 id, + const struct clk_pll_layout *layout, + const struct clk_pll_characteristics *characteristics); +struct clk * +at91_clk_register_plldiv(struct regmap *regmap, const char *name, + const char *parent_name); + +struct clk * +at91_clk_register_programmable(struct regmap *regmap, const char *name, + const char **parent_names, u8 num_parents, u8 id, + const struct clk_programmable_layout *layout); + +struct clk * +at91_clk_register_sam9260_slow(struct regmap *regmap, + const char *name, + const char **parent_names, + int num_parents); + +struct clk * +at91sam9x5_clk_register_smd(struct regmap *regmap, const char *name, + const char **parent_names, u8 num_parents); + +struct clk * +at91_clk_register_system(struct regmap *regmap, const char *name, + const char *parent_name, u8 id); + +struct clk * +at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name, + const char **parent_names, u8 num_parents); +struct clk * +at91sam9n12_clk_register_usb(struct regmap *regmap, const char *name, + const char *parent_name); +struct clk * +at91rm9200_clk_register_usb(struct regmap *regmap, const char *name, + const char *parent_name, const u32 *divisors); + +struct clk * +at91_clk_register_utmi(struct regmap *regmap_pmc, struct regmap *regmap_sfr, + const char *name, const char *parent_name); + +#ifdef CONFIG_PM +void pmc_register_id(u8 id); +void pmc_register_pck(u8 pck); +#else +static inline void pmc_register_id(u8 id) {} +static inline void pmc_register_pck(u8 pck) {} +#endif + #endif /* __PMC_H_ */ diff --git a/drivers/clk/at91/sama5d2.c b/drivers/clk/at91/sama5d2.c new file mode 100644 index 0000000000..dc15f7d9cb --- /dev/null +++ b/drivers/clk/at91/sama5d2.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <driver.h> +#include <regmap.h> +#include <stdio.h> +#include <mfd/syscon.h> + +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <dt-bindings/clock/at91.h> + +#include "pmc.h" + +static const struct clk_master_characteristics mck_characteristics = { + .output = { .min = 124000000, .max = 166000000 }, + .divisors = { 1, 2, 4, 3 }, +}; + +static u8 plla_out[] = { 0 }; + +static u16 plla_icpll[] = { 0 }; + +static struct clk_range plla_outputs[] = { + { .min = 600000000, .max = 1200000000 }, +}; + +static const struct clk_pll_characteristics plla_characteristics = { + .input = { .min = 12000000, .max = 12000000 }, + .num_output = ARRAY_SIZE(plla_outputs), + .output = plla_outputs, + .icpll = plla_icpll, + .out = plla_out, +}; + +static const struct { + char *n; + char *p; + u8 id; +} sama5d2_systemck[] = { + { .n = "ddrck", .p = "masterck", .id = 2 }, + { .n = "lcdck", .p = "masterck", .id = 3 }, + { .n = "uhpck", .p = "usbck", .id = 6 }, + { .n = "udpck", .p = "usbck", .id = 7 }, + { .n = "pck0", .p = "prog0", .id = 8 }, + { .n = "pck1", .p = "prog1", .id = 9 }, + { .n = "pck2", .p = "prog2", .id = 10 }, + { .n = "iscck", .p = "masterck", .id = 18 }, +}; + +static const struct { + char *n; + u8 id; + struct clk_range r; +} sama5d2_periph32ck[] = { + { .n = "macb0_clk", .id = 5, .r = { .min = 0, .max = 83000000 }, }, + { .n = "tdes_clk", .id = 11, .r = { .min = 0, .max = 83000000 }, }, + { .n = "matrix1_clk", .id = 14, }, + { .n = "hsmc_clk", .id = 17, }, + { .n = "pioA_clk", .id = 18, .r = { .min = 0, .max = 83000000 }, }, + { .n = "flx0_clk", .id = 19, .r = { .min = 0, .max = 83000000 }, }, + { .n = "flx1_clk", .id = 20, .r = { .min = 0, .max = 83000000 }, }, + { .n = "flx2_clk", .id = 21, .r = { .min = 0, .max = 83000000 }, }, + { .n = "flx3_clk", .id = 22, .r = { .min = 0, .max = 83000000 }, }, + { .n = "flx4_clk", .id = 23, .r = { .min = 0, .max = 83000000 }, }, + { .n = "uart0_clk", .id = 24, .r = { .min = 0, .max = 83000000 }, }, + { .n = "uart1_clk", .id = 25, .r = { .min = 0, .max = 83000000 }, }, + { .n = "uart2_clk", .id = 26, .r = { .min = 0, .max = 83000000 }, }, + { .n = "uart3_clk", .id = 27, .r = { .min = 0, .max = 83000000 }, }, + { .n = "uart4_clk", .id = 28, .r = { .min = 0, .max = 83000000 }, }, + { .n = "twi0_clk", .id = 29, .r = { .min = 0, .max = 83000000 }, }, + { .n = "twi1_clk", .id = 30, .r = { .min = 0, .max = 83000000 }, }, + { .n = "spi0_clk", .id = 33, .r = { .min = 0, .max = 83000000 }, }, + { .n = "spi1_clk", .id = 34, .r = { .min = 0, .max = 83000000 }, }, + { .n = "tcb0_clk", .id = 35, .r = { .min = 0, .max = 83000000 }, }, + { .n = "tcb1_clk", .id = 36, .r = { .min = 0, .max = 83000000 }, }, + { .n = "pwm_clk", .id = 38, .r = { .min = 0, .max = 83000000 }, }, + { .n = "adc_clk", .id = 40, .r = { .min = 0, .max = 83000000 }, }, + { .n = "uhphs_clk", .id = 41, .r = { .min = 0, .max = 83000000 }, }, + { .n = "udphs_clk", .id = 42, .r = { .min = 0, .max = 83000000 }, }, + { .n = "ssc0_clk", .id = 43, .r = { .min = 0, .max = 83000000 }, }, + { .n = "ssc1_clk", .id = 44, .r = { .min = 0, .max = 83000000 }, }, + { .n = "trng_clk", .id = 47, .r = { .min = 0, .max = 83000000 }, }, + { .n = "pdmic_clk", .id = 48, .r = { .min = 0, .max = 83000000 }, }, + { .n = "securam_clk", .id = 51, }, + { .n = "i2s0_clk", .id = 54, .r = { .min = 0, .max = 83000000 }, }, + { .n = "i2s1_clk", .id = 55, .r = { .min = 0, .max = 83000000 }, }, + { .n = "can0_clk", .id = 56, .r = { .min = 0, .max = 83000000 }, }, + { .n = "can1_clk", .id = 57, .r = { .min = 0, .max = 83000000 }, }, + { .n = "classd_clk", .id = 59, .r = { .min = 0, .max = 83000000 }, }, +}; + +static const struct { + char *n; + u8 id; +} sama5d2_periphck[] = { + { .n = "dma0_clk", .id = 6, }, + { .n = "dma1_clk", .id = 7, }, + { .n = "aes_clk", .id = 9, }, + { .n = "aesb_clk", .id = 10, }, + { .n = "sha_clk", .id = 12, }, + { .n = "mpddr_clk", .id = 13, }, + { .n = "matrix0_clk", .id = 15, }, + { .n = "sdmmc0_hclk", .id = 31, }, + { .n = "sdmmc1_hclk", .id = 32, }, + { .n = "lcdc_clk", .id = 45, }, + { .n = "isc_clk", .id = 46, }, + { .n = "qspi0_clk", .id = 52, }, + { .n = "qspi1_clk", .id = 53, }, +}; + +static const struct { + char *n; + u8 id; + struct clk_range r; + bool pll; +} sama5d2_gck[] = { + { .n = "sdmmc0_gclk", .id = 31, }, + { .n = "sdmmc1_gclk", .id = 32, }, + { .n = "tcb0_gclk", .id = 35, .r = { .min = 0, .max = 83000000 }, }, + { .n = "tcb1_gclk", .id = 36, .r = { .min = 0, .max = 83000000 }, }, + { .n = "pwm_gclk", .id = 38, .r = { .min = 0, .max = 83000000 }, }, + { .n = "isc_gclk", .id = 46, }, + { .n = "pdmic_gclk", .id = 48, }, + { .n = "i2s0_gclk", .id = 54, .pll = true }, + { .n = "i2s1_gclk", .id = 55, .pll = true }, + { .n = "can0_gclk", .id = 56, .r = { .min = 0, .max = 80000000 }, }, + { .n = "can1_gclk", .id = 57, .r = { .min = 0, .max = 80000000 }, }, + { .n = "classd_gclk", .id = 59, .r = { .min = 0, .max = 100000000 }, + .pll = true }, +}; + +static void __init sama5d2_pmc_setup(struct device_node *np) +{ + struct clk_range range = CLK_RANGE(0, 0); + const char *slck_name, *mainxtal_name; + struct pmc_data *sama5d2_pmc; + const char *parent_names[6]; + struct regmap *regmap, *regmap_sfr; + struct clk *hw; + int i; + bool bypass; + + i = of_property_match_string(np, "clock-names", "slow_clk"); + if (i < 0) + return; + + slck_name = of_clk_get_parent_name(np, i); + + i = of_property_match_string(np, "clock-names", "main_xtal"); + if (i < 0) + return; + mainxtal_name = of_clk_get_parent_name(np, i); + + regmap = syscon_node_to_regmap(np); + if (IS_ERR(regmap)) + return; + + sama5d2_pmc = pmc_data_allocate(PMC_I2S1_MUX + 1, + nck(sama5d2_systemck), + nck(sama5d2_periph32ck), + nck(sama5d2_gck)); + if (!sama5d2_pmc) + return; + + hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000, + 100000000); + if (IS_ERR(hw)) + goto err_free; + + bypass = of_property_read_bool(np, "atmel,osc-bypass"); + + hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name, + bypass); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = "main_rc_osc"; + parent_names[1] = "main_osc"; + hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2); + if (IS_ERR(hw)) + goto err_free; + + sama5d2_pmc->chws[PMC_MAIN] = hw; + + hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0, + &sama5d3_pll_layout, &plla_characteristics); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_plldiv(regmap, "plladivck", "pllack"); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_audio_pll_frac(regmap, "audiopll_fracck", + "mainck"); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_audio_pll_pad(regmap, "audiopll_padck", + "audiopll_fracck"); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_audio_pll_pmc(regmap, "audiopll_pmcck", + "audiopll_fracck"); + if (IS_ERR(hw)) + goto err_free; + + regmap_sfr = syscon_regmap_lookup_by_compatible("atmel,sama5d2-sfr"); + if (IS_ERR(regmap_sfr)) + regmap_sfr = NULL; + + hw = at91_clk_register_utmi(regmap, regmap_sfr, "utmick", "mainck"); + if (IS_ERR(hw)) + goto err_free; + + sama5d2_pmc->chws[PMC_UTMI] = hw; + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "plladivck"; + parent_names[3] = "utmick"; + hw = at91_clk_register_master(regmap, "masterck", 4, parent_names, + &at91sam9x5_master_layout, + &mck_characteristics); + if (IS_ERR(hw)) + goto err_free; + + sama5d2_pmc->chws[PMC_MCK] = hw; + + hw = at91_clk_register_h32mx(regmap, "h32mxck", "masterck"); + if (IS_ERR(hw)) + goto err_free; + + sama5d2_pmc->chws[PMC_MCK2] = hw; + + parent_names[0] = "plladivck"; + parent_names[1] = "utmick"; + hw = at91sam9x5_clk_register_usb(regmap, "usbck", parent_names, 2); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "plladivck"; + parent_names[3] = "utmick"; + parent_names[4] = "mck"; + for (i = 0; i < 3; i++) { + char *name; + + name = xasprintf("prog%d", i); + + hw = at91_clk_register_programmable(regmap, name, + parent_names, 5, i, + &at91sam9x5_programmable_layout); + if (IS_ERR(hw)) + goto err_free; + } + + for (i = 0; i < ARRAY_SIZE(sama5d2_systemck); i++) { + hw = at91_clk_register_system(regmap, sama5d2_systemck[i].n, + sama5d2_systemck[i].p, + sama5d2_systemck[i].id); + if (IS_ERR(hw)) + goto err_free; + + sama5d2_pmc->shws[sama5d2_systemck[i].id] = hw; + } + + for (i = 0; i < ARRAY_SIZE(sama5d2_periphck); i++) { + hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock, + sama5d2_periphck[i].n, + "masterck", + sama5d2_periphck[i].id, + &range); + if (IS_ERR(hw)) + goto err_free; + + sama5d2_pmc->phws[sama5d2_periphck[i].id] = hw; + } + + for (i = 0; i < ARRAY_SIZE(sama5d2_periph32ck); i++) { + hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock, + sama5d2_periph32ck[i].n, + "h32mxck", + sama5d2_periph32ck[i].id, + &sama5d2_periph32ck[i].r); + if (IS_ERR(hw)) + goto err_free; + + sama5d2_pmc->phws[sama5d2_periph32ck[i].id] = hw; + } + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "plladivck"; + parent_names[3] = "utmick"; + parent_names[4] = "mck"; + parent_names[5] = "audiopll_pmcck"; + for (i = 0; i < ARRAY_SIZE(sama5d2_gck); i++) { + hw = at91_clk_register_generated(regmap, &pmc_pcr_lock, + sama5d2_gck[i].n, + parent_names, 6, + sama5d2_gck[i].id, + sama5d2_gck[i].pll, + &sama5d2_gck[i].r); + if (IS_ERR(hw)) + goto err_free; + + sama5d2_pmc->ghws[sama5d2_gck[i].id] = hw; + } + + if (regmap_sfr) { + parent_names[0] = "i2s0_clk"; + parent_names[1] = "i2s0_gclk"; + hw = at91_clk_i2s_mux_register(regmap_sfr, "i2s0_muxclk", + parent_names, 2, 0); + if (IS_ERR(hw)) + goto err_free; + + sama5d2_pmc->chws[PMC_I2S0_MUX] = hw; + + parent_names[0] = "i2s1_clk"; + parent_names[1] = "i2s1_gclk"; + hw = at91_clk_i2s_mux_register(regmap_sfr, "i2s1_muxclk", + parent_names, 2, 1); + if (IS_ERR(hw)) + goto err_free; + + sama5d2_pmc->chws[PMC_I2S1_MUX] = hw; + } + + of_clk_add_provider(np, of_clk_hw_pmc_get, sama5d2_pmc); + + return; + +err_free: + pmc_data_free(sama5d2_pmc); +} +CLK_OF_DECLARE_DRIVER(sama5d2_pmc, "atmel,sama5d2-pmc", sama5d2_pmc_setup); diff --git a/drivers/clk/at91/sama5d4.c b/drivers/clk/at91/sama5d4.c new file mode 100644 index 0000000000..2fbfca6f85 --- /dev/null +++ b/drivers/clk/at91/sama5d4.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <driver.h> +#include <regmap.h> +#include <stdio.h> +#include <mfd/syscon.h> + +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <dt-bindings/clock/at91.h> + +#include "pmc.h" + +static const struct clk_master_characteristics mck_characteristics = { + .output = { .min = 125000000, .max = 200000000 }, + .divisors = { 1, 2, 4, 3 }, +}; + +static u8 plla_out[] = { 0 }; + +static u16 plla_icpll[] = { 0 }; + +static struct clk_range plla_outputs[] = { + { .min = 600000000, .max = 1200000000 }, +}; + +static const struct clk_pll_characteristics plla_characteristics = { + .input = { .min = 12000000, .max = 12000000 }, + .num_output = ARRAY_SIZE(plla_outputs), + .output = plla_outputs, + .icpll = plla_icpll, + .out = plla_out, +}; + +static const struct { + char *n; + char *p; + u8 id; +} sama5d4_systemck[] = { + { .n = "ddrck", .p = "masterck", .id = 2 }, + { .n = "lcdck", .p = "masterck", .id = 3 }, + { .n = "smdck", .p = "smdclk", .id = 4 }, + { .n = "uhpck", .p = "usbck", .id = 6 }, + { .n = "udpck", .p = "usbck", .id = 7 }, + { .n = "pck0", .p = "prog0", .id = 8 }, + { .n = "pck1", .p = "prog1", .id = 9 }, + { .n = "pck2", .p = "prog2", .id = 10 }, +}; + +static const struct { + char *n; + u8 id; +} sama5d4_periph32ck[] = { + { .n = "pioD_clk", .id = 5 }, + { .n = "usart0_clk", .id = 6 }, + { .n = "usart1_clk", .id = 7 }, + { .n = "icm_clk", .id = 9 }, + { .n = "aes_clk", .id = 12 }, + { .n = "tdes_clk", .id = 14 }, + { .n = "sha_clk", .id = 15 }, + { .n = "matrix1_clk", .id = 17 }, + { .n = "hsmc_clk", .id = 22 }, + { .n = "pioA_clk", .id = 23 }, + { .n = "pioB_clk", .id = 24 }, + { .n = "pioC_clk", .id = 25 }, + { .n = "pioE_clk", .id = 26 }, + { .n = "uart0_clk", .id = 27 }, + { .n = "uart1_clk", .id = 28 }, + { .n = "usart2_clk", .id = 29 }, + { .n = "usart3_clk", .id = 30 }, + { .n = "usart4_clk", .id = 31 }, + { .n = "twi0_clk", .id = 32 }, + { .n = "twi1_clk", .id = 33 }, + { .n = "twi2_clk", .id = 34 }, + { .n = "mci0_clk", .id = 35 }, + { .n = "mci1_clk", .id = 36 }, + { .n = "spi0_clk", .id = 37 }, + { .n = "spi1_clk", .id = 38 }, + { .n = "spi2_clk", .id = 39 }, + { .n = "tcb0_clk", .id = 40 }, + { .n = "tcb1_clk", .id = 41 }, + { .n = "tcb2_clk", .id = 42 }, + { .n = "pwm_clk", .id = 43 }, + { .n = "adc_clk", .id = 44 }, + { .n = "dbgu_clk", .id = 45 }, + { .n = "uhphs_clk", .id = 46 }, + { .n = "udphs_clk", .id = 47 }, + { .n = "ssc0_clk", .id = 48 }, + { .n = "ssc1_clk", .id = 49 }, + { .n = "trng_clk", .id = 53 }, + { .n = "macb0_clk", .id = 54 }, + { .n = "macb1_clk", .id = 55 }, + { .n = "fuse_clk", .id = 57 }, + { .n = "securam_clk", .id = 59 }, + { .n = "smd_clk", .id = 61 }, + { .n = "twi3_clk", .id = 62 }, + { .n = "catb_clk", .id = 63 }, +}; + +static const struct { + char *n; + u8 id; +} sama5d4_periphck[] = { + { .n = "dma0_clk", .id = 8 }, + { .n = "cpkcc_clk", .id = 10 }, + { .n = "aesb_clk", .id = 13 }, + { .n = "mpddr_clk", .id = 16 }, + { .n = "matrix0_clk", .id = 18 }, + { .n = "vdec_clk", .id = 19 }, + { .n = "dma1_clk", .id = 50 }, + { .n = "lcdc_clk", .id = 51 }, + { .n = "isi_clk", .id = 52 }, +}; + +static void __init sama5d4_pmc_setup(struct device_node *np) +{ + struct clk_range range = CLK_RANGE(0, 0); + const char *slck_name, *mainxtal_name; + struct pmc_data *sama5d4_pmc; + const char *parent_names[5]; + struct regmap *regmap; + struct clk *hw; + int i; + bool bypass; + + i = of_property_match_string(np, "clock-names", "slow_clk"); + if (i < 0) + return; + + slck_name = of_clk_get_parent_name(np, i); + + i = of_property_match_string(np, "clock-names", "main_xtal"); + if (i < 0) + return; + mainxtal_name = of_clk_get_parent_name(np, i); + + regmap = syscon_node_to_regmap(np); + if (IS_ERR(regmap)) + return; + + sama5d4_pmc = pmc_data_allocate(PMC_MCK2 + 1, + nck(sama5d4_systemck), + nck(sama5d4_periph32ck), 0); + if (!sama5d4_pmc) + return; + + hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000, + 100000000); + if (IS_ERR(hw)) + goto err_free; + + bypass = of_property_read_bool(np, "atmel,osc-bypass"); + + hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name, + bypass); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = "main_rc_osc"; + parent_names[1] = "main_osc"; + hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0, + &sama5d3_pll_layout, &plla_characteristics); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_plldiv(regmap, "plladivck", "pllack"); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_utmi(regmap, NULL, "utmick", "mainck"); + if (IS_ERR(hw)) + goto err_free; + + sama5d4_pmc->chws[PMC_UTMI] = hw; + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "plladivck"; + parent_names[3] = "utmick"; + hw = at91_clk_register_master(regmap, "masterck", 4, parent_names, + &at91sam9x5_master_layout, + &mck_characteristics); + if (IS_ERR(hw)) + goto err_free; + + sama5d4_pmc->chws[PMC_MCK] = hw; + + hw = at91_clk_register_h32mx(regmap, "h32mxck", "masterck"); + if (IS_ERR(hw)) + goto err_free; + + sama5d4_pmc->chws[PMC_MCK2] = hw; + + parent_names[0] = "plladivck"; + parent_names[1] = "utmick"; + hw = at91sam9x5_clk_register_usb(regmap, "usbck", parent_names, 2); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = "plladivck"; + parent_names[1] = "utmick"; + hw = at91sam9x5_clk_register_smd(regmap, "smdclk", parent_names, 2); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "plladivck"; + parent_names[3] = "utmick"; + parent_names[4] = "mck"; + for (i = 0; i < 3; i++) { + char *name; + + name = xasprintf("prog%d", i); + + hw = at91_clk_register_programmable(regmap, name, + parent_names, 5, i, + &at91sam9x5_programmable_layout); + if (IS_ERR(hw)) + goto err_free; + } + + for (i = 0; i < ARRAY_SIZE(sama5d4_systemck); i++) { + hw = at91_clk_register_system(regmap, sama5d4_systemck[i].n, + sama5d4_systemck[i].p, + sama5d4_systemck[i].id); + if (IS_ERR(hw)) + goto err_free; + + sama5d4_pmc->shws[sama5d4_systemck[i].id] = hw; + } + + for (i = 0; i < ARRAY_SIZE(sama5d4_periphck); i++) { + hw = at91_clk_register_sam9x5_peripheral(regmap, + sama5d4_periphck[i].n, + "masterck", + sama5d4_periphck[i].id, + &range); + if (IS_ERR(hw)) + goto err_free; + + sama5d4_pmc->phws[sama5d4_periphck[i].id] = hw; + } + + for (i = 0; i < ARRAY_SIZE(sama5d4_periph32ck); i++) { + hw = at91_clk_register_sam9x5_peripheral(regmap, + sama5d4_periph32ck[i].n, + "h32mxck", + sama5d4_periph32ck[i].id, + &range); + if (IS_ERR(hw)) + goto err_free; + + sama5d4_pmc->phws[sama5d4_periph32ck[i].id] = hw; + } + + of_clk_add_provider(np, of_clk_hw_pmc_get, sama5d4_pmc); + + return; + +err_free: + pmc_data_free(sama5d4_pmc); +} +CLK_OF_DECLARE_DRIVER(sama5d4_pmc, "atmel,sama5d4-pmc", sama5d4_pmc_setup); diff --git a/drivers/clk/clk-bulk.c b/drivers/clk/clk-bulk.c new file mode 100644 index 0000000000..ddbe32f9c2 --- /dev/null +++ b/drivers/clk/clk-bulk.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2017 NXP + * + * Dong Aisheng <aisheng.dong@nxp.com> + */ + +#include <common.h> +#include <errno.h> +#include <malloc.h> +#include <stringlist.h> +#include <complete.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/clk/clk-conf.h> + +void clk_bulk_put(int num_clks, struct clk_bulk_data *clks) +{ + while (--num_clks >= 0) { + clk_put(clks[num_clks].clk); + clks[num_clks].clk = NULL; + } +} +EXPORT_SYMBOL_GPL(clk_bulk_put); + +int __must_check clk_bulk_get(struct device_d *dev, int num_clks, + struct clk_bulk_data *clks) +{ + int ret; + int i; + + for (i = 0; i < num_clks; i++) + clks[i].clk = NULL; + + for (i = 0; i < num_clks; i++) { + clks[i].clk = clk_get(dev, clks[i].id); + if (IS_ERR(clks[i].clk)) { + ret = PTR_ERR(clks[i].clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get clk '%s': %d\n", + clks[i].id, ret); + clks[i].clk = NULL; + goto err; + } + } + + return 0; + +err: + clk_bulk_put(i, clks); + + return ret; +} +EXPORT_SYMBOL(clk_bulk_get); + +/** + * clk_bulk_disable - gate a set of clocks + * @num_clks: the number of clk_bulk_data + * @clks: the clk_bulk_data table being gated + * + * clk_bulk_disable must not sleep, which differentiates it from + * clk_bulk_unprepare. clk_bulk_disable must be called before + * clk_bulk_unprepare. + */ +void clk_bulk_disable(int num_clks, const struct clk_bulk_data *clks) +{ + + while (--num_clks >= 0) + clk_disable(clks[num_clks].clk); +} +EXPORT_SYMBOL_GPL(clk_bulk_disable); + +/** + * clk_bulk_enable - ungate a set of clocks + * @num_clks: the number of clk_bulk_data + * @clks: the clk_bulk_data table being ungated + * + * clk_bulk_enable must not sleep + * Returns 0 on success, -EERROR otherwise. + */ +int __must_check clk_bulk_enable(int num_clks, const struct clk_bulk_data *clks) +{ + int ret; + int i; + + for (i = 0; i < num_clks; i++) { + ret = clk_enable(clks[i].clk); + if (ret) { + pr_err("Failed to enable clk '%s': %d\n", + clks[i].id, ret); + goto err; + } + } + + return 0; + +err: + clk_bulk_disable(i, clks); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_bulk_enable); diff --git a/drivers/clk/clkdev.c b/drivers/clk/clkdev.c index abdc415272..f67a5c4d98 100644 --- a/drivers/clk/clkdev.c +++ b/drivers/clk/clkdev.c @@ -189,11 +189,6 @@ struct clk *clk_get(struct device_d *dev, const char *con_id) } EXPORT_SYMBOL(clk_get); -void clk_put(struct clk *clk) -{ -} -EXPORT_SYMBOL(clk_put); - void clkdev_add(struct clk_lookup *cl) { if (cl->dev_id) diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 7b04663d2e..b0502c3036 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -69,7 +69,6 @@ config CLOCKSOURCE_ROCKCHIP config CLOCKSOURCE_ATMEL_PIT bool - depends on SOC_AT91SAM9 || SOC_SAMA5 config CLOCKSOURCE_ARMV8_TIMER bool diff --git a/drivers/clocksource/efi_x86.c b/drivers/clocksource/efi_x86.c index 4d2657ea1d..f8d3ff8a43 100644 --- a/drivers/clocksource/efi_x86.c +++ b/drivers/clocksource/efi_x86.c @@ -6,7 +6,7 @@ #include <clock.h> #ifdef __x86_64__ -uint64_t ticks_read(void) +static uint64_t ticks_read(void) { uint64_t a, d; @@ -15,7 +15,7 @@ uint64_t ticks_read(void) return (d << 32) | a; } #else -uint64_t ticks_read(void) +static uint64_t ticks_read(void) { uint64_t val; diff --git a/drivers/efi/efi-device.c b/drivers/efi/efi-device.c index 5cc68fb781..305d337aab 100644 --- a/drivers/efi/efi-device.c +++ b/drivers/efi/efi-device.c @@ -32,7 +32,7 @@ #include <efi/efi-device.h> #include <linux/err.h> -int efi_locate_handle(enum efi_locate_search_type search_type, +static int efi_locate_handle(enum efi_locate_search_type search_type, efi_guid_t *protocol, void *search_key, unsigned long *no_handles, diff --git a/drivers/hab/habv3.c b/drivers/hab/habv3.c index 47d3caf864..f3f94bc44c 100644 --- a/drivers/hab/habv3.c +++ b/drivers/hab/habv3.c @@ -10,9 +10,11 @@ */ #define pr_fmt(fmt) "HABv3: " fmt +#include <init.h> #include <common.h> #include <hab.h> #include <io.h> +#include <mach/generic.h> struct hab_status { u8 value; @@ -55,7 +57,7 @@ static struct hab_status hab_status[] = { { 0x8e, "algorithm type is either invalid or ortherwise unsupported" }, }; -int imx_habv3_get_status(uint32_t status) +static int imx_habv3_get_status(uint32_t status) { int i; diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 56259d82d4..fc314ec9c6 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -16,3 +16,6 @@ config I2C_MUX source drivers/i2c/muxes/Kconfig endif + +config I2C_IMX_EARLY + bool diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 8dccc38379..61d7c86e76 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_I2C_AT91) += i2c-at91.o obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o obj-$(CONFIG_I2C_IMX) += i2c-imx.o +lwl-$(CONFIG_I2C_IMX_EARLY) += i2c-imx-early.o obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o obj-$(CONFIG_I2C_OMAP) += i2c-omap.o obj-$(CONFIG_I2C_TEGRA) += i2c-tegra.o diff --git a/drivers/i2c/busses/i2c-imx-early.c b/drivers/i2c/busses/i2c-imx-early.c new file mode 100644 index 0000000000..d67226441e --- /dev/null +++ b/drivers/i2c/busses/i2c-imx-early.c @@ -0,0 +1,310 @@ +/* + * Copyright 2013 GE Intelligent Platforms, Inc + * Copyright 2006,2009 Freescale Semiconductor, Inc. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Early I2C support functions to read SPD data or board + * information. + * Based on U-Boot drivers/i2c/fsl_i2c.c + */ +#include <common.h> +#include <i2c/i2c.h> +#include <i2c/i2c-early.h> + +#include "i2c-imx.h" + +struct fsl_i2c { + void __iomem *regs; + unsigned int i2cr_ien_opcode; + unsigned int i2sr_clr_opcode; + unsigned int ifdr; + unsigned int regshift; +}; + +static inline void fsl_i2c_write_reg(unsigned int val, + struct fsl_i2c *fsl_i2c, + unsigned int reg) +{ + reg <<= fsl_i2c->regshift; + + writeb(val, fsl_i2c->regs + reg); +} + +static inline unsigned char fsl_i2c_read_reg(struct fsl_i2c *fsl_i2c, + unsigned int reg) +{ + reg <<= fsl_i2c->regshift; + + return readb(fsl_i2c->regs + reg); +} + +static int i2c_fsl_poll_status(struct fsl_i2c *fsl_i2c, uint8_t set, uint8_t clear) +{ + int timeout = 1000000; + uint8_t temp; + + while (1) { + temp = fsl_i2c_read_reg(fsl_i2c, FSL_I2C_I2SR); + if (temp & set) + return 0; + if (~temp & clear) + return 0; + + if (!--timeout) { + pr_debug("timeout waiting for status %s 0x%02x, cur status: 0x%02x\n", + set ? "set" : "clear", + set ? set : clear, + temp); + return -EIO; + } + } +} + +static int i2c_fsl_bus_busy(struct fsl_i2c *fsl_i2c) +{ + return i2c_fsl_poll_status(fsl_i2c, I2SR_IBB, 0); +} + +static int i2c_fsl_bus_idle(struct fsl_i2c *fsl_i2c) +{ + return i2c_fsl_poll_status(fsl_i2c, 0, I2SR_IBB); +} + +static int i2c_fsl_trx_complete(struct fsl_i2c *fsl_i2c) +{ + int ret; + + ret = i2c_fsl_poll_status(fsl_i2c, I2SR_IIF, 0); + if (ret) + return ret; + + fsl_i2c_write_reg(fsl_i2c->i2sr_clr_opcode, + fsl_i2c, FSL_I2C_I2SR); + + return 0; +} + +static int i2c_fsl_acked(struct fsl_i2c *fsl_i2c) +{ + return i2c_fsl_poll_status(fsl_i2c, 0, I2SR_RXAK); +} + +static int i2c_fsl_start(struct fsl_i2c *fsl_i2c) +{ + unsigned int temp = 0; + int ret; + + fsl_i2c_write_reg(fsl_i2c->ifdr, fsl_i2c, FSL_I2C_IFDR); + + /* Enable I2C controller */ + fsl_i2c_write_reg(fsl_i2c->i2sr_clr_opcode, + fsl_i2c, FSL_I2C_I2SR); + fsl_i2c_write_reg(fsl_i2c->i2cr_ien_opcode, + fsl_i2c, FSL_I2C_I2CR); + + /* Wait controller to be stable */ + udelay(100); + + /* Start I2C transaction */ + temp = fsl_i2c_read_reg(fsl_i2c, FSL_I2C_I2CR); + temp |= I2CR_MSTA; + fsl_i2c_write_reg(temp, fsl_i2c, FSL_I2C_I2CR); + + ret = i2c_fsl_bus_busy(fsl_i2c); + if (ret) + return -EAGAIN; + + temp |= I2CR_MTX | I2CR_TXAK; + fsl_i2c_write_reg(temp, fsl_i2c, FSL_I2C_I2CR); + + return ret; +} + +static void i2c_fsl_stop(struct fsl_i2c *fsl_i2c) +{ + unsigned int temp = 0; + + /* Stop I2C transaction */ + temp = fsl_i2c_read_reg(fsl_i2c, FSL_I2C_I2CR); + temp &= ~(I2CR_MSTA | I2CR_MTX); + fsl_i2c_write_reg(temp, fsl_i2c, FSL_I2C_I2CR); + /* wait for the stop condition to be send, otherwise the i2c + * controller is disabled before the STOP is sent completely */ + + i2c_fsl_bus_idle(fsl_i2c); +} + +static int i2c_fsl_send(struct fsl_i2c *fsl_i2c, uint8_t data) +{ + int ret; + + pr_debug("%s send 0x%02x\n", __func__, data); + + fsl_i2c_write_reg(data, fsl_i2c, FSL_I2C_I2DR); + + ret = i2c_fsl_trx_complete(fsl_i2c); + if (ret) { + pr_debug("%s timeout 1\n", __func__); + return ret; + } + + ret = i2c_fsl_acked(fsl_i2c); + if (ret) { + pr_debug("%s timeout 2\n", __func__); + return ret; + } + + return 0; +} + +static int i2c_fsl_write(struct fsl_i2c *fsl_i2c, struct i2c_msg *msg) +{ + int i, ret; + + if (!(msg->flags & I2C_M_DATA_ONLY)) { + ret = i2c_fsl_send(fsl_i2c, msg->addr << 1); + if (ret) + return ret; + } + + /* write data */ + for (i = 0; i < msg->len; i++) { + ret = i2c_fsl_send(fsl_i2c, msg->buf[i]); + if (ret) + return ret; + } + + return 0; +} + +static int i2c_fsl_read(struct fsl_i2c *fsl_i2c, struct i2c_msg *msg) +{ + int i, ret; + unsigned int temp; + + /* clear IIF */ + fsl_i2c_write_reg(fsl_i2c->i2sr_clr_opcode, + fsl_i2c, FSL_I2C_I2SR); + + if (!(msg->flags & I2C_M_DATA_ONLY)) { + ret = i2c_fsl_send(fsl_i2c, (msg->addr << 1) | 1); + if (ret) + return ret; + } + + /* setup bus to read data */ + temp = fsl_i2c_read_reg(fsl_i2c, FSL_I2C_I2CR); + temp &= ~I2CR_MTX; + if (msg->len - 1) + temp &= ~I2CR_TXAK; + fsl_i2c_write_reg(temp, fsl_i2c, FSL_I2C_I2CR); + + fsl_i2c_read_reg(fsl_i2c, FSL_I2C_I2DR); /* dummy read */ + + /* read data */ + for (i = 0; i < msg->len; i++) { + ret = i2c_fsl_trx_complete(fsl_i2c); + if (ret) + return ret; + + if (i == (msg->len - 1)) { + i2c_fsl_stop(fsl_i2c); + } else if (i == (msg->len - 2)) { + temp = fsl_i2c_read_reg(fsl_i2c, FSL_I2C_I2CR); + temp |= I2CR_TXAK; + fsl_i2c_write_reg(temp, fsl_i2c, FSL_I2C_I2CR); + } + msg->buf[i] = fsl_i2c_read_reg(fsl_i2c, FSL_I2C_I2DR); + } + return 0; +} + +/** + * i2c_fsl_xfer - transfer I2C messages on i.MX compatible I2C controllers + * @ctx: driver context pointer + * @msgs: pointer to I2C messages + * @num: number of messages to transfer + * + * This function transfers I2C messages on i.MX and compatible I2C controllers. + * If successful returns the number of messages transferred, otherwise a negative + * error code is returned. + */ +int i2c_fsl_xfer(void *ctx, struct i2c_msg *msgs, int num) +{ + struct fsl_i2c *fsl_i2c = ctx; + unsigned int i, temp; + int ret; + + pr_debug("%s enter\n", __func__); + + /* Start I2C transfer */ + for (i = 0; i < 3; i++) { + ret = i2c_fsl_start(fsl_i2c); + if (!ret) + break; + if (ret == -EAGAIN) + continue; + return ret; + } + + /* read/write data */ + for (i = 0; i < num; i++) { + if (i && !(msgs[i].flags & I2C_M_DATA_ONLY)) { + temp = fsl_i2c_read_reg(fsl_i2c, FSL_I2C_I2CR); + temp |= I2CR_RSTA; + fsl_i2c_write_reg(temp, fsl_i2c, FSL_I2C_I2CR); + + ret = i2c_fsl_bus_busy(fsl_i2c); + if (ret) + goto fail0; + } + + /* write/read data */ + if (msgs[i].flags & I2C_M_RD) + ret = i2c_fsl_read(fsl_i2c, &msgs[i]); + else + ret = i2c_fsl_write(fsl_i2c, &msgs[i]); + if (ret) + goto fail0; + } + +fail0: + /* Stop I2C transfer */ + i2c_fsl_stop(fsl_i2c); + + /* Disable I2C controller, and force our state to stopped */ + temp = fsl_i2c->i2cr_ien_opcode ^ I2CR_IEN, + fsl_i2c_write_reg(temp, fsl_i2c, FSL_I2C_I2CR); + + return (ret < 0) ? ret : num; +} + +static struct fsl_i2c fsl_i2c; + +/** + * ls1046_i2c_init - Return a context pointer for accessing I2C on LS1046a + * @regs: The base address of the I2C controller to access + * + * This function returns a context pointer suitable to transfer I2C messages + * using i2c_fsl_xfer. + */ +void *ls1046_i2c_init(void __iomem *regs) +{ + fsl_i2c.regs = regs; + fsl_i2c.regshift = 0; + fsl_i2c.i2cr_ien_opcode = I2CR_IEN_OPCODE_0; + fsl_i2c.i2sr_clr_opcode = I2SR_CLR_OPCODE_W1C; + /* Divider for ~100kHz when coming from the ROM */ + fsl_i2c.ifdr = 0x3e; + + return &fsl_i2c; +} diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index 67937da73a..4c7346063c 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -49,61 +49,7 @@ #include <i2c/i2c.h> #include <mach/clock.h> -/* This will be the driver name */ -#define DRIVER_NAME "i2c-fsl" - -/* Default value */ -#define FSL_I2C_BIT_RATE 100000 /* 100kHz */ - -/* IMX I2C registers: - * the I2C register offset is different between SoCs, - * to provid support for all these chips, split the - * register offset into a fixed base address and a - * variable shift value, then the full register offset - * will be calculated by - * reg_off = ( reg_base_addr << reg_shift) - */ -#define FSL_I2C_IADR 0x00 /* i2c slave address */ -#define FSL_I2C_IFDR 0x01 /* i2c frequency divider */ -#define FSL_I2C_I2CR 0x02 /* i2c control */ -#define FSL_I2C_I2SR 0x03 /* i2c status */ -#define FSL_I2C_I2DR 0x04 /* i2c transfer data */ -#define FSL_I2C_DFSRR 0x14 /* i2c digital filter sampling rate */ - -#define IMX_I2C_REGSHIFT 2 -#define VF610_I2C_REGSHIFT 0 - - -/* Bits of FSL I2C registers */ -#define I2SR_RXAK 0x01 -#define I2SR_IIF 0x02 -#define I2SR_SRW 0x04 -#define I2SR_IAL 0x10 -#define I2SR_IBB 0x20 -#define I2SR_IAAS 0x40 -#define I2SR_ICF 0x80 -#define I2CR_RSTA 0x04 -#define I2CR_TXAK 0x08 -#define I2CR_MTX 0x10 -#define I2CR_MSTA 0x20 -#define I2CR_IIEN 0x40 -#define I2CR_IEN 0x80 - -/* register bits different operating codes definition: - * 1) I2SR: Interrupt flags clear operation differ between SoCs: - * - write zero to clear(w0c) INT flag on i.MX, - * - but write one to clear(w1c) INT flag on Vybrid. - * 2) I2CR: I2C module enable operation also differ between SoCs: - * - set I2CR_IEN bit enable the module on i.MX, - * - but clear I2CR_IEN bit enable the module on Vybrid. - */ -#define I2SR_CLR_OPCODE_W0C 0x0 -#define I2SR_CLR_OPCODE_W1C (I2SR_IAL | I2SR_IIF) -#define I2CR_IEN_OPCODE_0 0x0 -#define I2CR_IEN_OPCODE_1 I2CR_IEN - -#define I2C_PM_TIMEOUT 10 /* ms */ - +#include "i2c-imx.h" /* * sorted list of clock divider, register value pairs @@ -168,7 +114,6 @@ struct fsl_i2c_struct { struct clk *clk; struct i2c_adapter adapter; unsigned int disable_delay; - int stopped; unsigned int ifdr; /* FSL_I2C_IFDR */ unsigned int dfsrr; /* FSL_I2C_DFSRR */ struct i2c_bus_recovery_info rinfo; @@ -191,7 +136,6 @@ static inline unsigned char fsl_i2c_read_reg(struct fsl_i2c_struct *i2c_fsl, return readb(i2c_fsl->base + reg); } -#ifdef CONFIG_I2C_DEBUG static void i2c_fsl_dump_reg(struct i2c_adapter *adapter) { struct fsl_i2c_struct *i2c_fsl = to_fsl_i2c_struct(adapter); @@ -213,54 +157,51 @@ static void i2c_fsl_dump_reg(struct i2c_adapter *adapter) (reg_sr & I2SR_SRW ? 1 : 0), (reg_sr & I2SR_IIF ? 1 : 0), (reg_sr & I2SR_RXAK ? 1 : 0)); } -#else -static inline void i2c_fsl_dump_reg(struct i2c_adapter *adapter) -{ - return; -} -#endif - -static int i2c_fsl_bus_busy(struct i2c_adapter *adapter, int for_busy) +static int i2c_fsl_poll_status(struct i2c_adapter *adapter, int timeout_ms, + uint8_t set, uint8_t clear) { struct fsl_i2c_struct *i2c_fsl = to_fsl_i2c_struct(adapter); uint64_t start; - unsigned int temp; + uint8_t temp; start = get_time_ns(); while (1) { temp = fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2SR); - if (for_busy && (temp & I2SR_IBB)) - break; - if (!for_busy && !(temp & I2SR_IBB)) - break; - if (is_timeout(start, 500 * MSECOND)) { - dev_err(&adapter->dev, - "<%s> timeout waiting for I2C bus %s\n", - __func__,for_busy ? "busy" : "not busy"); + if (temp & set) + return 0; + if (~temp & clear) + return 0; + + if (is_timeout(start, timeout_ms * MSECOND)) { + dev_dbg(&adapter->dev, + "timeout waiting for status %s 0x%02x, cur status: 0x%02x\n", + set ? "set" : "clear", + set ? set : clear, + temp); return -EIO; } } +} - return 0; +static int i2c_fsl_bus_busy(struct i2c_adapter *adapter) +{ + return i2c_fsl_poll_status(adapter, 500, I2SR_IBB, 0); +} + +static int i2c_fsl_bus_idle(struct i2c_adapter *adapter) +{ + return i2c_fsl_poll_status(adapter, 500, 0, I2SR_IBB); } static int i2c_fsl_trx_complete(struct i2c_adapter *adapter) { struct fsl_i2c_struct *i2c_fsl = to_fsl_i2c_struct(adapter); - uint64_t start; - - start = get_time_ns(); - while (1) { - unsigned int reg = fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2SR); - if (reg & I2SR_IIF) - break; + int ret; - if (is_timeout(start, 100 * MSECOND)) { - dev_err(&adapter->dev, "<%s> TXR timeout\n", __func__); - return -EIO; - } - } + ret = i2c_fsl_poll_status(adapter, 100, I2SR_IIF, 0); + if (ret) + return ret; fsl_i2c_write_reg(i2c_fsl->hwdata->i2sr_clr_opcode, i2c_fsl, FSL_I2C_I2SR); @@ -270,22 +211,7 @@ static int i2c_fsl_trx_complete(struct i2c_adapter *adapter) static int i2c_fsl_acked(struct i2c_adapter *adapter) { - struct fsl_i2c_struct *i2c_fsl = to_fsl_i2c_struct(adapter); - uint64_t start; - - start = get_time_ns(); - while (1) { - unsigned int reg = fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2SR); - if (!(reg & I2SR_RXAK)) - break; - - if (is_timeout(start, MSECOND)) { - dev_dbg(&adapter->dev, "<%s> No ACK\n", __func__); - return -EIO; - } - } - - return 0; + return i2c_fsl_poll_status(adapter, 1, 0, I2SR_RXAK); } static int i2c_fsl_start(struct i2c_adapter *adapter) @@ -314,7 +240,7 @@ static int i2c_fsl_start(struct i2c_adapter *adapter) temp |= I2CR_MSTA; fsl_i2c_write_reg(temp, i2c_fsl, FSL_I2C_I2CR); - result = i2c_fsl_bus_busy(adapter, 1); + result = i2c_fsl_bus_busy(adapter); if (result) { result = i2c_recover_bus(&i2c_fsl->adapter); if (result) @@ -322,8 +248,6 @@ static int i2c_fsl_start(struct i2c_adapter *adapter) return -EAGAIN; } - i2c_fsl->stopped = 0; - temp |= I2CR_MTX | I2CR_TXAK; fsl_i2c_write_reg(temp, i2c_fsl, FSL_I2C_I2CR); @@ -335,24 +259,20 @@ static void i2c_fsl_stop(struct i2c_adapter *adapter) struct fsl_i2c_struct *i2c_fsl = to_fsl_i2c_struct(adapter); unsigned int temp = 0; - if (!i2c_fsl->stopped) { - /* Stop I2C transaction */ - temp = fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2CR); - temp &= ~(I2CR_MSTA | I2CR_MTX); - fsl_i2c_write_reg(temp, i2c_fsl, FSL_I2C_I2CR); - /* wait for the stop condition to be send, otherwise the i2c - * controller is disabled before the STOP is sent completely */ - i2c_fsl->stopped = i2c_fsl_bus_busy(adapter, 0) ? 0 : 1; - } - - if (!i2c_fsl->stopped) { - i2c_fsl_bus_busy(adapter, 0); - i2c_fsl->stopped = 1; - } + /* Stop I2C transaction */ + temp = fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2CR); + if (!(temp & I2CR_MSTA)) + return; - /* Disable I2C controller, and force our state to stopped */ - temp = i2c_fsl->hwdata->i2cr_ien_opcode ^ I2CR_IEN, + temp &= ~(I2CR_MSTA | I2CR_MTX); fsl_i2c_write_reg(temp, i2c_fsl, FSL_I2C_I2CR); + /* wait for the stop condition to be send, otherwise the i2c + * controller is disabled before the STOP is sent completely */ + + /* adding this delay helps on low bitrates */ + udelay(i2c_fsl->disable_delay); + + i2c_fsl_bus_idle(adapter); } #ifdef CONFIG_PPC @@ -462,45 +382,44 @@ static void i2c_fsl_set_clk(struct fsl_i2c_struct *i2c_fsl, } #endif -static int i2c_fsl_write(struct i2c_adapter *adapter, struct i2c_msg *msgs) +static int i2c_fsl_send(struct i2c_adapter *adapter, uint8_t data) { struct fsl_i2c_struct *i2c_fsl = to_fsl_i2c_struct(adapter); - int i, result; + int result; - if ( !(msgs->flags & I2C_M_DATA_ONLY) ) { - dev_dbg(&adapter->dev, - "<%s> write slave address: addr=0x%02x\n", - __func__, msgs->addr << 1); + dev_dbg(&adapter->dev, "<%s> send 0x%02x\n", __func__, data); - /* write slave address */ - fsl_i2c_write_reg(msgs->addr << 1, i2c_fsl, FSL_I2C_I2DR); + fsl_i2c_write_reg(data, i2c_fsl, FSL_I2C_I2DR); - result = i2c_fsl_trx_complete(adapter); - if (result) - return result; - result = i2c_fsl_acked(adapter); + result = i2c_fsl_trx_complete(adapter); + if (result) + return result; + + return i2c_fsl_acked(adapter); +} + +static int i2c_fsl_write(struct i2c_adapter *adapter, struct i2c_msg *msg) +{ + int i, result; + + if (!(msg->flags & I2C_M_DATA_ONLY)) { + result = i2c_fsl_send(adapter, msg->addr << 1); if (result) return result; } /* write data */ - for (i = 0; i < msgs->len; i++) { - dev_dbg(&adapter->dev, - "<%s> write byte: B%d=0x%02X\n", - __func__, i, msgs->buf[i]); - fsl_i2c_write_reg(msgs->buf[i], i2c_fsl, FSL_I2C_I2DR); - - result = i2c_fsl_trx_complete(adapter); - if (result) - return result; - result = i2c_fsl_acked(adapter); + for (i = 0; i < msg->len; i++) { + result = i2c_fsl_send(adapter, msg->buf[i]); if (result) return result; } + return 0; } -static int i2c_fsl_read(struct i2c_adapter *adapter, struct i2c_msg *msgs) +static int i2c_fsl_read(struct i2c_adapter *adapter, struct i2c_msg *msg, + bool is_last) { struct fsl_i2c_struct *i2c_fsl = to_fsl_i2c_struct(adapter); int i, result; @@ -510,18 +429,8 @@ static int i2c_fsl_read(struct i2c_adapter *adapter, struct i2c_msg *msgs) fsl_i2c_write_reg(i2c_fsl->hwdata->i2sr_clr_opcode, i2c_fsl, FSL_I2C_I2SR); - if ( !(msgs->flags & I2C_M_DATA_ONLY) ) { - dev_dbg(&adapter->dev, - "<%s> write slave address: addr=0x%02x\n", - __func__, (msgs->addr << 1) | 0x01); - - /* write slave address */ - fsl_i2c_write_reg((msgs->addr << 1) | 0x01, i2c_fsl, FSL_I2C_I2DR); - - result = i2c_fsl_trx_complete(adapter); - if (result) - return result; - result = i2c_fsl_acked(adapter); + if (!(msg->flags & I2C_M_DATA_ONLY)) { + result = i2c_fsl_send(adapter, (msg->addr << 1) | 1); if (result) return result; } @@ -529,43 +438,29 @@ static int i2c_fsl_read(struct i2c_adapter *adapter, struct i2c_msg *msgs) /* setup bus to read data */ temp = fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2CR); temp &= ~I2CR_MTX; - if (msgs->len - 1) + if (msg->len - 1) temp &= ~I2CR_TXAK; fsl_i2c_write_reg(temp, i2c_fsl, FSL_I2C_I2CR); fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2DR); /* dummy read */ /* read data */ - for (i = 0; i < msgs->len; i++) { + for (i = 0; i < msg->len; i++) { result = i2c_fsl_trx_complete(adapter); if (result) return result; - if (i == (msgs->len - 1)) { - /* - * It must generate STOP before read I2DR to prevent - * controller from generating another clock cycle - */ - temp = fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2CR); - temp &= ~(I2CR_MSTA | I2CR_MTX); - fsl_i2c_write_reg(temp, i2c_fsl, FSL_I2C_I2CR); - - /* - * adding this delay helps on low bitrates - */ - udelay(i2c_fsl->disable_delay); - - i2c_fsl_bus_busy(adapter, 0); - i2c_fsl->stopped = 1; - } else if (i == (msgs->len - 2)) { + if (is_last && i == msg->len - 1) { + i2c_fsl_stop(adapter); + } else if (i == (msg->len - 2)) { temp = fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2CR); temp |= I2CR_TXAK; fsl_i2c_write_reg(temp, i2c_fsl, FSL_I2C_I2CR); } - msgs->buf[i] = fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2DR); + msg->buf[i] = fsl_i2c_read_reg(i2c_fsl, FSL_I2C_I2DR); dev_dbg(&adapter->dev, "<%s> read byte: B%d=0x%02X\n", - __func__, i, msgs->buf[i]); + __func__, i, msg->buf[i]); } return 0; } @@ -594,7 +489,7 @@ static int i2c_fsl_xfer(struct i2c_adapter *adapter, temp |= I2CR_RSTA; fsl_i2c_write_reg(temp, i2c_fsl, FSL_I2C_I2CR); - result = i2c_fsl_bus_busy(adapter, 1); + result = i2c_fsl_bus_busy(adapter); if (result) goto fail0; } @@ -602,7 +497,7 @@ static int i2c_fsl_xfer(struct i2c_adapter *adapter, /* write/read data */ if (msgs[i].flags & I2C_M_RD) - result = i2c_fsl_read(adapter, &msgs[i]); + result = i2c_fsl_read(adapter, &msgs[i], i == num - 1); else result = i2c_fsl_write(adapter, &msgs[i]); if (result) @@ -613,6 +508,10 @@ fail0: /* Stop I2C transfer */ i2c_fsl_stop(adapter); + /* Disable I2C controller, and force our state to stopped */ + temp = i2c_fsl->hwdata->i2cr_ien_opcode ^ I2CR_IEN, + fsl_i2c_write_reg(temp, i2c_fsl, FSL_I2C_I2CR); + return (result < 0) ? result : num; } @@ -664,6 +563,7 @@ static int __init i2c_fsl_probe(struct device_d *pdev) struct fsl_i2c_struct *i2c_fsl; struct i2c_platform_data *pdata; int ret; + int bitrate; pdata = pdev->platform_data; @@ -705,10 +605,12 @@ static int __init i2c_fsl_probe(struct device_d *pdev) i2c_fsl->dfsrr = -1; /* Set up clock divider */ + bitrate = 100000; + of_property_read_u32(pdev->device_node, "clock-frequency", &bitrate); if (pdata && pdata->bitrate) - i2c_fsl_set_clk(i2c_fsl, pdata->bitrate); - else - i2c_fsl_set_clk(i2c_fsl, FSL_I2C_BIT_RATE); + bitrate = pdata->bitrate; + + i2c_fsl_set_clk(i2c_fsl, bitrate); /* Set up chip registers to defaults */ fsl_i2c_write_reg(i2c_fsl->hwdata->i2cr_ien_opcode ^ I2CR_IEN, @@ -753,9 +655,7 @@ static __maybe_unused struct of_device_id imx_i2c_dt_ids[] = { static struct driver_d i2c_fsl_driver = { .probe = i2c_fsl_probe, - .name = DRIVER_NAME, -#ifndef CONFIG_PPC + .name = "i2c-fsl", .of_compatible = DRV_OF_COMPAT(imx_i2c_dt_ids), -#endif }; coredevice_platform_driver(i2c_fsl_driver); diff --git a/drivers/i2c/busses/i2c-imx.h b/drivers/i2c/busses/i2c-imx.h new file mode 100644 index 0000000000..3e3e1317f2 --- /dev/null +++ b/drivers/i2c/busses/i2c-imx.h @@ -0,0 +1,52 @@ +#ifndef I2C_IMX_H +#define I2C_IMX_H + +/* + * IMX I2C registers: + * the I2C register offset is different between SoCs, to provide support for + * all these chips, split the register offset into a fixed base address and a + * variable shift value, then the full register offset will be calculated by: + * reg_off = reg_base_addr << reg_shift + */ +#define FSL_I2C_IADR 0x00 /* i2c slave address */ +#define FSL_I2C_IFDR 0x01 /* i2c frequency divider */ +#define FSL_I2C_I2CR 0x02 /* i2c control */ +#define FSL_I2C_I2SR 0x03 /* i2c status */ +#define FSL_I2C_I2DR 0x04 /* i2c transfer data */ +#define FSL_I2C_DFSRR 0x05 /* i2c digital filter sampling rate */ + +#define IMX_I2C_REGSHIFT 2 +#define VF610_I2C_REGSHIFT 0 + +/* Bits of FSL I2C registers */ +#define I2SR_RXAK 0x01 +#define I2SR_IIF 0x02 +#define I2SR_SRW 0x04 +#define I2SR_IAL 0x10 +#define I2SR_IBB 0x20 +#define I2SR_IAAS 0x40 +#define I2SR_ICF 0x80 +#define I2CR_RSTA 0x04 +#define I2CR_TXAK 0x08 +#define I2CR_MTX 0x10 +#define I2CR_MSTA 0x20 +#define I2CR_IIEN 0x40 +#define I2CR_IEN 0x80 + +/* + * register bits different operating codes definition: + * + * 1) I2SR: Interrupt flags clear operation differ between SoCs: + * - write zero to clear(w0c) INT flag on i.MX, + * - but write one to clear(w1c) INT flag on Vybrid. + * + * 2) I2CR: I2C module enable operation also differ between SoCs: + * - set I2CR_IEN bit enable the module on i.MX, + * - but clear I2CR_IEN bit enable the module on Vybrid. + */ +#define I2SR_CLR_OPCODE_W0C 0x0 +#define I2SR_CLR_OPCODE_W1C (I2SR_IAL | I2SR_IIF) +#define I2CR_IEN_OPCODE_0 0x0 +#define I2CR_IEN_OPCODE_1 I2CR_IEN + +#endif /* I2C_IMX_H */ diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig index 2075151d67..cd28fefa43 100644 --- a/drivers/mci/Kconfig +++ b/drivers/mci/Kconfig @@ -93,6 +93,9 @@ config MCI_IMX_ESDHC_PIO help mostly useful for debugging. Normally you should use DMA. +config MCI_IMX_ESDHC_PBL + bool + config MCI_OMAP_HSMMC bool "OMAP HSMMC" depends on ARCH_OMAP4 || ARCH_OMAP3 || ARCH_AM33XX diff --git a/drivers/mci/Makefile b/drivers/mci/Makefile index fe2c8adbac..f6214c0cbb 100644 --- a/drivers/mci/Makefile +++ b/drivers/mci/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_MCI_BCM283X) += mci-bcm2835.o obj-$(CONFIG_MCI_DOVE) += dove-sdhci.o obj-$(CONFIG_MCI_IMX) += imx.o obj-$(CONFIG_MCI_IMX_ESDHC) += imx-esdhc.o +pbl-$(CONFIG_MCI_IMX_ESDHC_PBL) += imx-esdhc-pbl.o obj-$(CONFIG_MCI_MXS) += mxs.o obj-$(CONFIG_MCI_OMAP_HSMMC) += omap_hsmmc.o obj-$(CONFIG_MCI_PXA) += pxamci.o diff --git a/drivers/mci/imx-esdhc-pbl.c b/drivers/mci/imx-esdhc-pbl.c new file mode 100644 index 0000000000..f77530d310 --- /dev/null +++ b/drivers/mci/imx-esdhc-pbl.c @@ -0,0 +1,406 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#define pr_fmt(fmt) "xload-esdhc: " fmt + +#include <common.h> +#include <io.h> +#include <mci.h> +#include <linux/sizes.h> +#ifdef CONFIG_ARCH_IMX +#include <mach/atf.h> +#include <mach/imx6-regs.h> +#include <mach/imx8mq-regs.h> +#include <mach/xload.h> +#include <mach/imx-header.h> +#endif +#include "sdhci.h" +#include "imx-esdhc.h" + +#define SECTOR_SIZE 512 + +struct esdhc { + void __iomem *regs; + bool is_mx6; + bool is_be; +}; + +static uint32_t esdhc_read32(struct esdhc *esdhc, int reg) +{ + if (esdhc->is_be) + return in_be32(esdhc->regs + reg); + else + return readl(esdhc->regs + reg); +} + +static void esdhc_write32(struct esdhc *esdhc, int reg, uint32_t val) +{ + if (esdhc->is_be) + out_be32(esdhc->regs + reg, val); + else + writel(val, esdhc->regs + reg); +} + +static void __udelay(int us) +{ + volatile int i; + + for (i = 0; i < us * 4; i++); +} + +static u32 esdhc_xfertyp(struct mci_cmd *cmd, struct mci_data *data) +{ + u32 xfertyp = 0; + + if (data) + xfertyp |= COMMAND_DPSEL | TRANSFER_MODE_MSBSEL | + TRANSFER_MODE_BCEN |TRANSFER_MODE_DTDSEL; + + if (cmd->resp_type & MMC_RSP_CRC) + xfertyp |= COMMAND_CCCEN; + if (cmd->resp_type & MMC_RSP_OPCODE) + xfertyp |= COMMAND_CICEN; + if (cmd->resp_type & MMC_RSP_136) + xfertyp |= COMMAND_RSPTYP_136; + else if (cmd->resp_type & MMC_RSP_BUSY) + xfertyp |= COMMAND_RSPTYP_48_BUSY; + else if (cmd->resp_type & MMC_RSP_PRESENT) + xfertyp |= COMMAND_RSPTYP_48; + + return COMMAND_CMD(cmd->cmdidx) | xfertyp; +} + +static int esdhc_do_data(struct esdhc *esdhc, struct mci_data *data) +{ + char *buffer; + u32 databuf; + u32 size; + u32 irqstat; + u32 present; + + buffer = data->dest; + + size = data->blocksize * data->blocks; + irqstat = esdhc_read32(esdhc, SDHCI_INT_STATUS); + + while (size) { + int i; + int timeout = 1000000; + + while (1) { + present = esdhc_read32(esdhc, SDHCI_PRESENT_STATE) & PRSSTAT_BREN; + if (present) + break; + if (!--timeout) { + pr_err("read time out\n"); + return -ETIMEDOUT; + } + } + + for (i = 0; i < SECTOR_SIZE / sizeof(uint32_t); i++) { + databuf = esdhc_read32(esdhc, SDHCI_BUFFER); + *((u32 *)buffer) = databuf; + buffer += 4; + size -= 4; + } + } + + return 0; +} + +static int +esdhc_send_cmd(struct esdhc *esdhc, struct mci_cmd *cmd, struct mci_data *data) +{ + u32 xfertyp, mixctrl; + u32 irqstat; + int ret; + int timeout; + + esdhc_write32(esdhc, SDHCI_INT_STATUS, -1); + + /* Wait at least 8 SD clock cycles before the next command */ + __udelay(1); + + if (data) { + unsigned long dest = (unsigned long)data->dest; + + if (dest > 0xffffffff) + return -EINVAL; + + /* Set up for a data transfer if we have one */ + esdhc_write32(esdhc, SDHCI_DMA_ADDRESS, (u32)dest); + esdhc_write32(esdhc, SDHCI_BLOCK_SIZE__BLOCK_COUNT, data->blocks << 16 | SECTOR_SIZE); + } + + /* Figure out the transfer arguments */ + xfertyp = esdhc_xfertyp(cmd, data); + + /* Send the command */ + esdhc_write32(esdhc, SDHCI_ARGUMENT, cmd->cmdarg); + + if (esdhc->is_mx6) { + /* write lower-half of xfertyp to mixctrl */ + mixctrl = xfertyp & 0xFFFF; + /* Keep the bits 22-25 of the register as is */ + mixctrl |= (esdhc_read32(esdhc, IMX_SDHCI_MIXCTRL) & (0xF << 22)); + esdhc_write32(esdhc, IMX_SDHCI_MIXCTRL, mixctrl); + } + + esdhc_write32(esdhc, SDHCI_TRANSFER_MODE__COMMAND, xfertyp); + + /* Wait for the command to complete */ + timeout = 10000; + while (!(esdhc_read32(esdhc, SDHCI_INT_STATUS) & IRQSTAT_CC)) { + __udelay(1); + if (!timeout--) + return -ETIMEDOUT; + } + + irqstat = esdhc_read32(esdhc, SDHCI_INT_STATUS); + esdhc_write32(esdhc, SDHCI_INT_STATUS, irqstat); + + if (irqstat & CMD_ERR) + return -EIO; + + if (irqstat & IRQSTAT_CTOE) + return -ETIMEDOUT; + + /* Copy the response to the response buffer */ + cmd->response[0] = esdhc_read32(esdhc, SDHCI_RESPONSE_0); + + /* Wait until all of the blocks are transferred */ + if (data) { + ret = esdhc_do_data(esdhc, data); + if (ret) + return ret; + } + + esdhc_write32(esdhc, SDHCI_INT_STATUS, -1); + + /* Wait for the bus to be idle */ + timeout = 10000; + while (esdhc_read32(esdhc, SDHCI_PRESENT_STATE) & + (PRSSTAT_CICHB | PRSSTAT_CIDHB | PRSSTAT_DLA)) { + __udelay(1); + if (!timeout--) + return -ETIMEDOUT; + } + + return 0; +} + +static int esdhc_read_blocks(struct esdhc *esdhc, void *dst, size_t len) +{ + struct mci_cmd cmd; + struct mci_data data; + u32 val; + int ret; + + esdhc_write32(esdhc, SDHCI_INT_ENABLE, + IRQSTATEN_CC | IRQSTATEN_TC | IRQSTATEN_CINT | IRQSTATEN_CTOE | + IRQSTATEN_CCE | IRQSTATEN_CEBE | IRQSTATEN_CIE | + IRQSTATEN_DTOE | IRQSTATEN_DCE | IRQSTATEN_DEBE | + IRQSTATEN_DINT); + + esdhc_write32(esdhc, IMX_SDHCI_WML, 0x0); + + val = esdhc_read32(esdhc, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET); + val |= SYSCTL_HCKEN | SYSCTL_IPGEN; + esdhc_write32(esdhc, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET, val); + + cmd.cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1; + + data.dest = dst; + data.blocks = len / SECTOR_SIZE; + data.blocksize = SECTOR_SIZE; + data.flags = MMC_DATA_READ; + + ret = esdhc_send_cmd(esdhc, &cmd, &data); + if (ret) { + pr_debug("send command failed with %d\n", ret); + return ret; + } + + cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1b; + + esdhc_send_cmd(esdhc, &cmd, NULL); + + return 0; +} + +#ifdef CONFIG_ARCH_IMX +static int +esdhc_start_image(struct esdhc *esdhc, ptrdiff_t address, ptrdiff_t entry, u32 offset) +{ + + void *buf = (void *)address; + struct imx_flash_header_v2 *hdr; + int ret, len; + void __noreturn (*bb)(void); + unsigned int ofs; + int i, header_count = 1; + + len = imx_image_size(); + len = ALIGN(len, SECTOR_SIZE); + + for (i = 0; i < header_count; i++) { + ret = esdhc_read_blocks(esdhc, buf, + offset + SZ_1K + SECTOR_SIZE); + if (ret) + return ret; + + hdr = buf + offset + SZ_1K; + + if (!is_imx_flash_header_v2(hdr)) { + pr_debug("IVT header not found on SD card. " + "Found tag: 0x%02x length: 0x%04x " + "version: %02x\n", + hdr->header.tag, hdr->header.length, + hdr->header.version); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_ARCH_IMX8MQ) && + hdr->boot_data.plugin & PLUGIN_HDMI_IMAGE) { + /* + * In images that include signed HDMI + * firmware, first v2 header would be + * dedicated to that and would not contain any + * useful for us information. In order for us + * to pull the rest of the bootloader image + * in, we need to re-read header from SD/MMC, + * this time skipping anything HDMI firmware + * related. + */ + offset += hdr->boot_data.size + hdr->header.length; + header_count++; + } + } + + pr_debug("Check ok, loading image\n"); + + ofs = offset + hdr->entry - hdr->boot_data.start; + + if (entry != address) { + /* + * Passing entry different from address is interpreted + * as a request to place the image such that its entry + * point would be exactly at 'entry', that is: + * + * buf + ofs = entry + * + * solving the above for 'buf' gvies us the + * adjustement that needs to be made: + * + * buf = entry - ofs + * + */ + if (WARN_ON(entry - ofs < address)) { + /* + * We want to make sure we won't try to place + * the start of the image before the beginning + * of the memory buffer we were given in + * address. + */ + return -EINVAL; + } + + buf = (void *)(entry - ofs); + } + + ret = esdhc_read_blocks(esdhc, buf, offset + len); + if (ret) { + pr_err("Loading image failed with %d\n", ret); + return ret; + } + + pr_debug("Image loaded successfully\n"); + + bb = buf + ofs; + + bb(); +} + +/** + * imx6_esdhc_start_image - Load and start an image from USDHC controller + * @instance: The USDHC controller instance (0..4) + * + * This uses esdhc_start_image() to load an image from SD/MMC. It is + * assumed that the image is the currently running barebox image (This + * information is used to calculate the length of the image). The + * image is started afterwards. + * + * Return: If successful, this function does not return. A negative error + * code is returned when this function fails. + */ +int imx6_esdhc_start_image(int instance) +{ + struct esdhc esdhc; + + switch (instance) { + case 0: + esdhc.regs = IOMEM(MX6_USDHC1_BASE_ADDR); + break; + case 1: + esdhc.regs = IOMEM(MX6_USDHC2_BASE_ADDR); + break; + case 2: + esdhc.regs = IOMEM(MX6_USDHC3_BASE_ADDR); + break; + case 3: + esdhc.regs = IOMEM(MX6_USDHC4_BASE_ADDR); + break; + default: + return -EINVAL; + } + + esdhc.is_mx6 = 1; + + return esdhc_start_image(&esdhc, 0x10000000, 0x10000000, 0); +} + +/** + * imx8_esdhc_start_image - Load and start an image from USDHC controller + * @instance: The USDHC controller instance (0..2) + * + * This uses esdhc_start_image() to load an image from SD/MMC. It is + * assumed that the image is the currently running barebox image (This + * information is used to calculate the length of the image). The + * image is started afterwards. + * + * Return: If successful, this function does not return. A negative error + * code is returned when this function fails. + */ +int imx8_esdhc_start_image(int instance) +{ + struct esdhc esdhc; + + switch (instance) { + case 0: + esdhc.regs = IOMEM(MX8MQ_USDHC1_BASE_ADDR); + break; + case 1: + esdhc.regs = IOMEM(MX8MQ_USDHC2_BASE_ADDR); + break; + default: + return -EINVAL; + } + + esdhc.is_mx6 = 1; + + return esdhc_start_image(&esdhc, MX8MQ_DDR_CSD1_BASE_ADDR, + MX8MQ_ATF_BL33_BASE_ADDR, SZ_32K); +} +#endif diff --git a/drivers/mci/imx-esdhc.c b/drivers/mci/imx-esdhc.c index a9c5440758..cedfb3db42 100644 --- a/drivers/mci/imx-esdhc.c +++ b/drivers/mci/imx-esdhc.c @@ -83,15 +83,6 @@ /* The IP supports HS400 mode */ #define ESDHC_FLAG_HS400 BIT(9) - -#define IMX_SDHCI_WML 0x44 -#define IMX_SDHCI_MIXCTRL 0x48 -#define IMX_SDHCI_DLL_CTRL 0x60 -#define IMX_SDHCI_MIX_CTRL_FBCLK_SEL (BIT(25)) - -#define ESDHC_DMA_SYSCTL 0x40c /* Layerscape specific */ -#define ESDHC_SYSCTL_DMA_SNOOP BIT(6) - struct esdhc_soc_data { u32 flags; const char *clkidx; diff --git a/drivers/mci/imx-esdhc.h b/drivers/mci/imx-esdhc.h index 9003843abb..9b79346f90 100644 --- a/drivers/mci/imx-esdhc.h +++ b/drivers/mci/imx-esdhc.h @@ -58,6 +58,14 @@ #define PIO_TIMEOUT 100000 +#define IMX_SDHCI_WML 0x44 +#define IMX_SDHCI_MIXCTRL 0x48 +#define IMX_SDHCI_DLL_CTRL 0x60 +#define IMX_SDHCI_MIX_CTRL_FBCLK_SEL BIT(25) + +#define ESDHC_DMA_SYSCTL 0x40c /* Layerscape specific */ +#define ESDHC_SYSCTL_DMA_SNOOP BIT(6) + struct fsl_esdhc_cfg { u32 esdhc_base; u32 no_snoop; diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c index 30ed65f737..b1ff1b1eac 100644 --- a/drivers/mfd/syscon.c +++ b/drivers/mfd/syscon.c @@ -160,6 +160,20 @@ struct regmap *syscon_node_to_regmap(struct device_node *np) return syscon->regmap; } +struct regmap *syscon_regmap_lookup_by_compatible(const char *s) +{ + struct device_node *syscon_np; + struct regmap *regmap; + + syscon_np = of_find_compatible_node(NULL, NULL, s); + if (!syscon_np) + return ERR_PTR(-ENODEV); + + regmap = syscon_node_to_regmap(syscon_np); + + return regmap; +} + static int syscon_probe(struct device_d *dev) { struct syscon *syscon; diff --git a/drivers/net/e1000/main.c b/drivers/net/e1000/main.c index 774e3d030f..f67c5d867b 100644 --- a/drivers/net/e1000/main.c +++ b/drivers/net/e1000/main.c @@ -3713,9 +3713,4 @@ static struct pci_driver e1000_eth_driver = { .probe = e1000_probe, .remove = e1000_remove, }; - -static int e1000_driver_init(void) -{ - return pci_register_driver(&e1000_eth_driver); -} -device_initcall(e1000_driver_init); +device_pci_driver(e1000_eth_driver); diff --git a/drivers/net/efi-snp.c b/drivers/net/efi-snp.c index 4e32513739..def2714bee 100644 --- a/drivers/net/efi-snp.c +++ b/drivers/net/efi-snp.c @@ -231,7 +231,7 @@ static int efi_snp_set_ethaddr(struct eth_device *edev, const unsigned char *adr return 0; } -int efi_snp_probe(struct efi_device *efidev) +static int efi_snp_probe(struct efi_device *efidev) { struct eth_device *edev; struct efi_snp_priv *priv; diff --git a/drivers/net/rtl8139.c b/drivers/net/rtl8139.c index cfa34a2f2a..e1c57e6b7c 100644 --- a/drivers/net/rtl8139.c +++ b/drivers/net/rtl8139.c @@ -594,9 +594,4 @@ static struct pci_driver rtl8139_eth_driver = { .id_table = rtl8139_pci_tbl, .probe = rtl8139_probe, }; - -static int rtl8139_init(void) -{ - return pci_register_driver(&rtl8139_eth_driver); -} -device_initcall(rtl8139_init); +device_pci_driver(rtl8139_eth_driver); diff --git a/drivers/net/rtl8169.c b/drivers/net/rtl8169.c index ba257509ee..4e9823d424 100644 --- a/drivers/net/rtl8169.c +++ b/drivers/net/rtl8169.c @@ -544,9 +544,4 @@ static struct pci_driver rtl8169_eth_driver = { .id_table = rtl8169_pci_tbl, .probe = rtl8169_probe, }; - -static int rtl8169_init(void) -{ - return pci_register_driver(&rtl8169_eth_driver); -} -device_initcall(rtl8169_init); +device_pci_driver(rtl8169_eth_driver); diff --git a/drivers/nvme/Kconfig b/drivers/nvme/Kconfig new file mode 100644 index 0000000000..27ac9654ac --- /dev/null +++ b/drivers/nvme/Kconfig @@ -0,0 +1,5 @@ +menu "NVME Support" + +source "drivers/nvme/host/Kconfig" + +endmenu diff --git a/drivers/nvme/Makefile b/drivers/nvme/Makefile new file mode 100644 index 0000000000..6d7d51c801 --- /dev/null +++ b/drivers/nvme/Makefile @@ -0,0 +1 @@ +obj-y += host/ diff --git a/drivers/nvme/host/Kconfig b/drivers/nvme/host/Kconfig new file mode 100644 index 0000000000..8888c8900b --- /dev/null +++ b/drivers/nvme/host/Kconfig @@ -0,0 +1,11 @@ +config NVME_CORE + bool + +config BLK_DEV_NVME + bool "NVM Express block device" + depends on PCI && BLOCK + select NVME_CORE + ---help--- + The NVM Express driver is for solid state drives directly + connected to the PCI or PCI Express bus. If you know you + don't have one of these, it is safe to answer N. diff --git a/drivers/nvme/host/Makefile b/drivers/nvme/host/Makefile new file mode 100644 index 0000000000..9afbc0d2e1 --- /dev/null +++ b/drivers/nvme/host/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 + +ccflags-y += -I$(src) + +obj-$(CONFIG_NVME_CORE) += nvme-core.o +obj-$(CONFIG_BLK_DEV_NVME) += nvme.o + +nvme-core-y := core.o +nvme-y += pci.o diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c new file mode 100644 index 0000000000..e0984708b4 --- /dev/null +++ b/drivers/nvme/host/core.c @@ -0,0 +1,614 @@ +#include <common.h> + +#include "nvme.h" + +int __nvme_submit_sync_cmd(struct nvme_ctrl *ctrl, + struct nvme_command *cmd, + union nvme_result *result, + void *buffer, unsigned bufflen, + unsigned timeout, int qid) +{ + return ctrl->ops->submit_sync_cmd(ctrl, cmd, result, buffer, bufflen, + timeout, qid); +} +EXPORT_SYMBOL_GPL(__nvme_submit_sync_cmd); + +int nvme_submit_sync_cmd(struct nvme_ctrl *ctrl, + struct nvme_command *cmd, + void *buffer, unsigned bufflen) +{ + return __nvme_submit_sync_cmd(ctrl, cmd, NULL, buffer, bufflen, 0, + NVME_QID_ADMIN); +} +EXPORT_SYMBOL_GPL(nvme_sec_submit); + +static int nvme_identify_ctrl(struct nvme_ctrl *dev, struct nvme_id_ctrl **id) +{ + struct nvme_command c = { }; + int error; + + /* gcc-4.4.4 (at least) has issues with initializers and anon unions */ + c.identify.opcode = nvme_admin_identify; + c.identify.cns = NVME_ID_CNS_CTRL; + + *id = kmalloc(sizeof(struct nvme_id_ctrl), GFP_KERNEL); + if (!*id) + return -ENOMEM; + + error = nvme_submit_sync_cmd(dev, &c, *id, + sizeof(struct nvme_id_ctrl)); + if (error) + kfree(*id); + + return error; +} + +static int +nvme_set_features(struct nvme_ctrl *dev, unsigned fid, unsigned dword11, + void *buffer, size_t buflen, u32 *result) +{ + struct nvme_command c; + union nvme_result res; + int ret; + + memset(&c, 0, sizeof(c)); + c.features.opcode = nvme_admin_set_features; + c.features.fid = cpu_to_le32(fid); + c.features.dword11 = cpu_to_le32(dword11); + + ret = __nvme_submit_sync_cmd(dev, &c, &res, buffer, buflen, 0, + NVME_QID_ADMIN); + if (ret >= 0 && result) + *result = le32_to_cpu(res.u32); + return ret; +} + +int nvme_set_queue_count(struct nvme_ctrl *ctrl, int *count) +{ + u32 q_count = (*count - 1) | ((*count - 1) << 16); + u32 result; + int status, nr_io_queues; + + status = nvme_set_features(ctrl, NVME_FEAT_NUM_QUEUES, q_count, NULL, 0, + &result); + if (status < 0) + return status; + + /* + * Degraded controllers might return an error when setting the queue + * count. We still want to be able to bring them online and offer + * access to the admin queue, as that might be only way to fix them up. + */ + if (status > 0) { + dev_err(ctrl->dev, "Could not set queue count (%d)\n", status); + *count = 0; + } else { + nr_io_queues = min(result & 0xffff, result >> 16) + 1; + *count = min(*count, nr_io_queues); + } + + return 0; +} +EXPORT_SYMBOL_GPL(nvme_set_queue_count); + +static int nvme_wait_ready(struct nvme_ctrl *ctrl, u64 cap, bool enabled) +{ + uint64_t start = get_time_ns(); + unsigned long timeout = + ((NVME_CAP_TIMEOUT(cap) + 1) * HZ / 2); + u32 csts, bit = enabled ? NVME_CSTS_RDY : 0; + int ret; + + while ((ret = ctrl->ops->reg_read32(ctrl, NVME_REG_CSTS, &csts)) == 0) { + if (csts == ~0) + return -ENODEV; + if ((csts & NVME_CSTS_RDY) == bit) + break; + + mdelay(100); + + if (is_timeout(start, timeout)) { + dev_err(ctrl->dev, + "Device not ready; aborting %s\n", enabled ? + "initialisation" : "reset"); + return -ENODEV; + } + } + + return ret; +} + +static int nvme_identify_ns_list(struct nvme_ctrl *dev, unsigned nsid, __le32 *ns_list) +{ + struct nvme_command c = { }; + + c.identify.opcode = nvme_admin_identify; + c.identify.cns = NVME_ID_CNS_NS_ACTIVE_LIST; + c.identify.nsid = cpu_to_le32(nsid); + return nvme_submit_sync_cmd(dev, &c, ns_list, NVME_IDENTIFY_DATA_SIZE); +} + +static struct nvme_id_ns *nvme_identify_ns(struct nvme_ctrl *ctrl, + unsigned nsid) +{ + struct nvme_id_ns *id; + struct nvme_command c = { }; + int error; + + /* gcc-4.4.4 (at least) has issues with initializers and anon unions */ + c.identify.opcode = nvme_admin_identify; + c.identify.nsid = cpu_to_le32(nsid); + c.identify.cns = NVME_ID_CNS_NS; + + id = kmalloc(sizeof(*id), GFP_KERNEL); + if (!id) + return NULL; + + error = nvme_submit_sync_cmd(ctrl, &c, id, sizeof(*id)); + if (error) { + dev_warn(ctrl->dev, "Identify namespace failed\n"); + kfree(id); + return NULL; + } + + return id; +} + +static struct nvme_ns_head *nvme_alloc_ns_head(struct nvme_ctrl *ctrl, + unsigned nsid, struct nvme_id_ns *id) +{ + static int instance = 1; + struct nvme_ns_head *head; + int ret = -ENOMEM; + + head = kzalloc(sizeof(*head), GFP_KERNEL); + if (!head) + goto out; + + head->instance = instance++; + head->ns_id = nsid; + + return head; +out: + return ERR_PTR(ret); +} + +static int nvme_init_ns_head(struct nvme_ns *ns, unsigned nsid, + struct nvme_id_ns *id) +{ + struct nvme_ctrl *ctrl = ns->ctrl; + const bool is_shared = id->nmic & (1 << 0); + struct nvme_ns_head *head = NULL; + + if (is_shared) { + dev_info(ctrl->dev, "Skipping shared namespace %u\n", nsid); + return -ENOTSUPP; + } + + head = nvme_alloc_ns_head(ctrl, nsid, id); + if (IS_ERR(head)) + return PTR_ERR(head); + + ns->head = head; + + return 0; +} + +#define DISK_NAME_LEN 32 + +static void nvme_update_disk_info(struct block_device *blk, struct nvme_ns *ns, + struct nvme_id_ns *id) +{ + blk->blockbits = ns->lba_shift; + blk->num_blocks = le64_to_cpup(&id->nsze); + + ns->readonly = id->nsattr & (1 << 0); +} + +static void __nvme_revalidate_disk(struct block_device *blk, + struct nvme_id_ns *id) +{ + struct nvme_ns *ns = to_nvme_ns(blk); + + /* + * If identify namespace failed, use default 512 byte block size so + * block layer can use before failing read/write for 0 capacity. + */ + ns->lba_shift = id->lbaf[id->flbas & NVME_NS_FLBAS_LBA_MASK].ds; + if (ns->lba_shift == 0) + ns->lba_shift = 9; + + nvme_update_disk_info(blk, ns, id); +} + +static void nvme_setup_rw(struct nvme_ns *ns, struct nvme_command *cmnd, + int block, int num_block) +{ + cmnd->rw.nsid = cpu_to_le32(ns->head->ns_id); + cmnd->rw.slba = cpu_to_le64(nvme_block_nr(ns, block)); + cmnd->rw.length = cpu_to_le16(num_block - 1); + cmnd->rw.control = 0; + cmnd->rw.dsmgmt = 0; +} + +static void nvme_setup_flush(struct nvme_ns *ns, struct nvme_command *cmnd) +{ + memset(cmnd, 0, sizeof(*cmnd)); + cmnd->common.opcode = nvme_cmd_flush; + cmnd->common.nsid = cpu_to_le32(ns->head->ns_id); +} + +static int nvme_submit_sync_rw(struct nvme_ns *ns, struct nvme_command *cmnd, + void *buffer, int block, int num_blocks) +{ + /* + * ns->ctrl->max_hw_sectors is in units of 512 bytes, so we + * need to make sure we adjust it to discovered lba_shift + */ + const u32 max_hw_sectors = + ns->ctrl->max_hw_sectors >> (ns->lba_shift - 9); + int ret; + + if (num_blocks > max_hw_sectors) { + while (num_blocks) { + const int chunk = min_t(int, num_blocks, + max_hw_sectors); + + ret = nvme_submit_sync_rw(ns, cmnd, buffer, block, + chunk); + if (ret) + break; + + num_blocks -= chunk; + buffer += chunk; + block += chunk; + } + + return ret; + } + + nvme_setup_rw(ns, cmnd, block, num_blocks); + + ret = __nvme_submit_sync_cmd(ns->ctrl, cmnd, NULL, buffer, + num_blocks << ns->lba_shift, + 0, NVME_QID_IO); + + if (ret) { + dev_err(ns->ctrl->dev, + "I/O failed: block: %d, num blocks: %d, status code type: %xh, status code %02xh\n", + block, num_blocks, (ret >> 8) & 0xf, + ret & 0xff); + return -EIO; + } + + return 0; +} + + +static int nvme_block_device_read(struct block_device *blk, void *buffer, + int block, int num_blocks) +{ + struct nvme_ns *ns = to_nvme_ns(blk); + struct nvme_command cmnd = { }; + + cmnd.rw.opcode = nvme_cmd_read; + + return nvme_submit_sync_rw(ns, &cmnd, buffer, block, num_blocks); +} + +static int __maybe_unused +nvme_block_device_write(struct block_device *blk, const void *buffer, + int block, int num_blocks) +{ + struct nvme_ns *ns = to_nvme_ns(blk); + struct nvme_command cmnd = { }; + + if (ns->readonly) + return -EINVAL; + + cmnd.rw.opcode = nvme_cmd_write; + + return nvme_submit_sync_rw(ns, &cmnd, (void *)buffer, block, + num_blocks); +} + +static int __maybe_unused nvme_block_device_flush(struct block_device *blk) +{ + struct nvme_ns *ns = to_nvme_ns(blk); + struct nvme_command cmnd = { }; + + nvme_setup_flush(ns, &cmnd); + + return __nvme_submit_sync_cmd(ns->ctrl, &cmnd, NULL, NULL, + 0, 0, NVME_QID_IO); +} + +static struct block_device_ops nvme_block_device_ops = { + .read = nvme_block_device_read, +#ifdef CONFIG_BLOCK_WRITE + .write = nvme_block_device_write, + .flush = nvme_block_device_flush, +#endif +}; + +static void nvme_alloc_ns(struct nvme_ctrl *ctrl, unsigned nsid) +{ + struct nvme_ns *ns; + struct nvme_id_ns *id; + char disk_name[DISK_NAME_LEN]; + int ret, flags; + + ns = kzalloc(sizeof(*ns), GFP_KERNEL); + if (!ns) + return; + + ns->ctrl = ctrl; + ns->lba_shift = 9; /* set to a default value for 512 until + * disk is validated */ + + id = nvme_identify_ns(ctrl, nsid); + if (!id) + goto out_free_ns; + + if (id->ncap == 0) + goto out_free_id; + + if (nvme_init_ns_head(ns, nsid, id)) + goto out_free_id; + + nvme_set_disk_name(disk_name, ns, ctrl, &flags); + + ns->blk.dev = ctrl->dev; + ns->blk.ops = &nvme_block_device_ops; + ns->blk.cdev.name = strdup(disk_name); + + __nvme_revalidate_disk(&ns->blk, id); + kfree(id); + + ret = blockdevice_register(&ns->blk); + if (ret) { + dev_err(ctrl->dev, "Cannot register block device (%d)\n", ret); + goto out_free_id; + } + + return; +out_free_id: + kfree(id); +out_free_ns: + kfree(ns); +} + +static int nvme_scan_ns_list(struct nvme_ctrl *ctrl, unsigned nn) +{ + __le32 *ns_list; + unsigned i, j, nsid, prev = 0, num_lists = DIV_ROUND_UP(nn, 1024); + int ret = 0; + + ns_list = kzalloc(NVME_IDENTIFY_DATA_SIZE, GFP_KERNEL); + if (!ns_list) + return -ENOMEM; + + for (i = 0; i < num_lists; i++) { + ret = nvme_identify_ns_list(ctrl, prev, ns_list); + if (ret) + goto out; + + for (j = 0; j < min(nn, 1024U); j++) { + nsid = le32_to_cpu(ns_list[j]); + if (!nsid) + goto out; + + nvme_alloc_ns(ctrl, nsid); + } + nn -= j; + } + out: + kfree(ns_list); + return ret; +} + +static void nvme_scan_ns_sequential(struct nvme_ctrl *ctrl, unsigned nn) +{ + unsigned i; + + for (i = 1; i <= nn; i++) + nvme_alloc_ns(ctrl, i); +} + +static void nvme_scan_work(struct nvme_ctrl *ctrl) +{ + struct nvme_id_ctrl *id; + unsigned nn; + + if (nvme_identify_ctrl(ctrl, &id)) + return; + + nn = le32_to_cpu(id->nn); + if (ctrl->vs >= NVME_VS(1, 1, 0)) { + if (!nvme_scan_ns_list(ctrl, nn)) + goto out_free_id; + } + nvme_scan_ns_sequential(ctrl, nn); +out_free_id: + kfree(id); +} + +void nvme_start_ctrl(struct nvme_ctrl *ctrl) +{ + if (ctrl->queue_count > 1) + nvme_scan_work(ctrl); +} +EXPORT_SYMBOL_GPL(nvme_start_ctrl); + +/* + * If the device has been passed off to us in an enabled state, just clear + * the enabled bit. The spec says we should set the 'shutdown notification + * bits', but doing so may cause the device to complete commands to the + * admin queue ... and we don't know what memory that might be pointing at! + */ +int nvme_disable_ctrl(struct nvme_ctrl *ctrl, u64 cap) +{ + int ret; + + ctrl->ctrl_config &= ~NVME_CC_SHN_MASK; + ctrl->ctrl_config &= ~NVME_CC_ENABLE; + + ret = ctrl->ops->reg_write32(ctrl, NVME_REG_CC, ctrl->ctrl_config); + if (ret) + return ret; + + return nvme_wait_ready(ctrl, cap, false); +} +EXPORT_SYMBOL_GPL(nvme_disable_ctrl); + +int nvme_enable_ctrl(struct nvme_ctrl *ctrl, u64 cap) +{ + /* + * Default to a 4K page size, with the intention to update this + * path in the future to accomodate architectures with differing + * kernel and IO page sizes. + */ + unsigned dev_page_min = NVME_CAP_MPSMIN(cap) + 12, page_shift = 12; + int ret; + + if (page_shift < dev_page_min) { + dev_err(ctrl->dev, + "Minimum device page size %u too large for host (%u)\n", + 1 << dev_page_min, 1 << page_shift); + return -ENODEV; + } + + ctrl->page_size = 1 << page_shift; + + ctrl->ctrl_config = NVME_CC_CSS_NVM; + ctrl->ctrl_config |= (page_shift - 12) << NVME_CC_MPS_SHIFT; + ctrl->ctrl_config |= NVME_CC_AMS_RR | NVME_CC_SHN_NONE; + ctrl->ctrl_config |= NVME_CC_IOSQES | NVME_CC_IOCQES; + ctrl->ctrl_config |= NVME_CC_ENABLE; + + ret = ctrl->ops->reg_write32(ctrl, NVME_REG_CC, ctrl->ctrl_config); + if (ret) + return ret; + return nvme_wait_ready(ctrl, cap, true); +} +EXPORT_SYMBOL_GPL(nvme_enable_ctrl); + +int nvme_shutdown_ctrl(struct nvme_ctrl *ctrl) +{ + uint64_t start = get_time_ns(); + unsigned long timeout = SHUTDOWN_TIMEOUT; + u32 csts; + int ret; + + ctrl->ctrl_config &= ~NVME_CC_SHN_MASK; + ctrl->ctrl_config |= NVME_CC_SHN_NORMAL; + + ret = ctrl->ops->reg_write32(ctrl, NVME_REG_CC, ctrl->ctrl_config); + if (ret) + return ret; + + while ((ret = ctrl->ops->reg_read32(ctrl, NVME_REG_CSTS, &csts)) == 0) { + if ((csts & NVME_CSTS_SHST_MASK) == NVME_CSTS_SHST_CMPLT) + break; + + mdelay(100); + + if (is_timeout(start, timeout)) { + dev_err(ctrl->dev, + "Device shutdown incomplete; abort shutdown\n"); + return -ENODEV; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(nvme_shutdown_ctrl); + +#define NVME_ID_MAX_LEN 41 + +static void nvme_print(struct nvme_ctrl *ctrl, const char *prefix, + const char *_string, size_t _length) +{ + char string[NVME_ID_MAX_LEN]; + const size_t length = min(_length, sizeof(string) - 1); + + memcpy(string, _string, length); + string[length - 1] = '\0'; + + dev_info(ctrl->dev, "%s: %s\n", prefix, string); +} + +static int nvme_init_subsystem(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id) +{ + nvme_print(ctrl, "serial", id->sn, sizeof(id->sn)); + nvme_print(ctrl, "model", id->mn, sizeof(id->mn)); + nvme_print(ctrl, "firmware", id->fr, sizeof(id->fr)); + + return 0; +} + +/* + * Initialize the cached copies of the Identify data and various controller + * register in our nvme_ctrl structure. This should be called as soon as + * the admin queue is fully up and running. + */ +int nvme_init_identify(struct nvme_ctrl *ctrl) +{ + struct nvme_id_ctrl *id; + u64 cap; + int ret, page_shift; + u32 max_hw_sectors; + + ret = ctrl->ops->reg_read32(ctrl, NVME_REG_VS, &ctrl->vs); + if (ret) { + dev_err(ctrl->dev, "Reading VS failed (%d)\n", ret); + return ret; + } + + ret = ctrl->ops->reg_read64(ctrl, NVME_REG_CAP, &cap); + if (ret) { + dev_err(ctrl->dev, "Reading CAP failed (%d)\n", ret); + return ret; + } + page_shift = NVME_CAP_MPSMIN(cap) + 12; + + ret = nvme_identify_ctrl(ctrl, &id); + if (ret) { + dev_err(ctrl->dev, "Identify Controller failed (%d)\n", ret); + return -EIO; + } + + ret = nvme_init_subsystem(ctrl, id); + if (ret) + return ret; + + if (id->mdts) + max_hw_sectors = 1 << (id->mdts + page_shift - 9); + else + max_hw_sectors = UINT_MAX; + ctrl->max_hw_sectors = + min_not_zero(ctrl->max_hw_sectors, max_hw_sectors); + + kfree(id); + return 0; +} +EXPORT_SYMBOL_GPL(nvme_init_identify); + + +/* + * Initialize a NVMe controller structures. This needs to be called during + * earliest initialization so that we have the initialized structured around + * during probing. + */ +int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device_d *dev, + const struct nvme_ctrl_ops *ops) +{ + static int instance = 0; + + ctrl->dev = dev; + ctrl->ops = ops; + ctrl->instance = instance++; + + return 0; +} +EXPORT_SYMBOL_GPL(nvme_init_ctrl); diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h new file mode 100644 index 0000000000..4ec4aef972 --- /dev/null +++ b/drivers/nvme/host/nvme.h @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2011-2014, Intel Corporation. + * + * 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. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef _NVME_H +#define _NVME_H + +#include <linux/nvme.h> +#include <dma.h> +#include <block.h> + +#define ADMIN_TIMEOUT (60 * HZ) +#define SHUTDOWN_TIMEOUT ( 5 * HZ) + +/* + * Common request structure for NVMe passthrough. All drivers must have + * this structure as the first member of their request-private data. + */ +struct nvme_request { + struct nvme_command *cmd; + union nvme_result result; + u16 status; + + void *buffer; + unsigned int buffer_len; + dma_addr_t buffer_dma_addr; + enum dma_data_direction dma_dir; +}; + +struct nvme_ctrl { + const struct nvme_ctrl_ops *ops; + struct device_d *dev; + int instance; + + u32 ctrl_config; + u32 queue_count; + u64 cap; + u32 page_size; + u32 max_hw_sectors; + u32 vs; +}; + +/* + * Anchor structure for namespaces. There is one for each namespace in a + * NVMe subsystem that any of our controllers can see, and the namespace + * structure for each controller is chained of it. For private namespaces + * there is a 1:1 relation to our namespace structures, that is ->list + * only ever has a single entry for private namespaces. + */ +struct nvme_ns_head { + unsigned ns_id; + int instance; +}; + +struct nvme_ns { + struct nvme_ctrl *ctrl; + struct nvme_ns_head *head; + struct block_device blk; + + int lba_shift; + bool readonly; +}; + +static inline struct nvme_ns *to_nvme_ns(struct block_device *blk) +{ + return container_of(blk, struct nvme_ns, blk); +} + +struct nvme_ctrl_ops { + int (*reg_read32)(struct nvme_ctrl *ctrl, u32 off, u32 *val); + int (*reg_write32)(struct nvme_ctrl *ctrl, u32 off, u32 val); + int (*reg_read64)(struct nvme_ctrl *ctrl, u32 off, u64 *val); + + int (*submit_sync_cmd)(struct nvme_ctrl *ctrl, + struct nvme_command *cmd, + union nvme_result *result, + void *buffer, + unsigned bufflen, + unsigned timeout, int qid); +}; + +static inline bool nvme_ctrl_ready(struct nvme_ctrl *ctrl) +{ + u32 val = 0; + + if (ctrl->ops->reg_read32(ctrl, NVME_REG_CSTS, &val)) + return false; + return val & NVME_CSTS_RDY; +} + +static inline u64 nvme_block_nr(struct nvme_ns *ns, sector_t sector) +{ + return (sector >> (ns->lba_shift - 9)); +} + +static inline void nvme_end_request(struct nvme_request *rq, __le16 status, + union nvme_result result) +{ + rq->status = le16_to_cpu(status) >> 1; + rq->result = result; +} + +int nvme_disable_ctrl(struct nvme_ctrl *ctrl, u64 cap); +int nvme_enable_ctrl(struct nvme_ctrl *ctrl, u64 cap); +int nvme_shutdown_ctrl(struct nvme_ctrl *ctrl); +int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device_d *dev, + const struct nvme_ctrl_ops *ops); +void nvme_start_ctrl(struct nvme_ctrl *ctrl); +int nvme_init_identify(struct nvme_ctrl *ctrl); + +enum nvme_queue_id { + NVME_QID_ADMIN, + NVME_QID_IO, + NVME_QID_NUM, + NVME_QID_ANY = -1, +}; + +int __nvme_submit_sync_cmd(struct nvme_ctrl *ctrl, + struct nvme_command *cmd, + union nvme_result *result, + void *buffer, unsigned bufflen, + unsigned timeout, int qid); +int nvme_submit_sync_cmd(struct nvme_ctrl *ctrl, + struct nvme_command *cmd, + void *buffer, unsigned bufflen); + + +int nvme_set_queue_count(struct nvme_ctrl *ctrl, int *count); +/* + * Without the multipath code enabled, multiple controller per subsystems are + * visible as devices and thus we cannot use the subsystem instance. + */ +static inline void nvme_set_disk_name(char *disk_name, struct nvme_ns *ns, + struct nvme_ctrl *ctrl, int *flags) +{ + sprintf(disk_name, "nvme%dn%d", ctrl->instance, ns->head->instance); +} + +#endif /* _NVME_H */ diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c new file mode 100644 index 0000000000..387bc45a7b --- /dev/null +++ b/drivers/nvme/host/pci.c @@ -0,0 +1,697 @@ + +#include <common.h> +#include <init.h> +#include <io.h> +#include <io-64-nonatomic-lo-hi.h> +#include <linux/pci.h> + +#include <dma.h> + +#include "nvme.h" + +#define SQ_SIZE(depth) (depth * sizeof(struct nvme_command)) +#define CQ_SIZE(depth) (depth * sizeof(struct nvme_completion)) + +#define NVME_MAX_KB_SZ 4096 + +static int io_queue_depth = 2; + +struct nvme_dev; + +/* + * An NVM Express queue. Each device has at least two (one for admin + * commands and one for I/O commands). + */ +struct nvme_queue { + struct nvme_dev *dev; + struct nvme_request *req; + struct nvme_command *sq_cmds; + volatile struct nvme_completion *cqes; + dma_addr_t sq_dma_addr; + dma_addr_t cq_dma_addr; + u32 __iomem *q_db; + u16 q_depth; + u16 sq_tail; + u16 cq_head; + u16 qid; + u8 cq_phase; + + u16 counter; +}; + +/* + * Represents an NVM Express device. Each nvme_dev is a PCI function. + */ +struct nvme_dev { + struct nvme_queue queues[NVME_QID_NUM]; + u32 __iomem *dbs; + struct device_d *dev; + unsigned online_queues; + unsigned max_qid; + int q_depth; + u32 db_stride; + void __iomem *bar; + bool subsystem; + struct nvme_ctrl ctrl; + __le64 *prp_pool; + unsigned int prp_pool_size; + dma_addr_t prp_dma; +}; + +static inline struct nvme_dev *to_nvme_dev(struct nvme_ctrl *ctrl) +{ + return container_of(ctrl, struct nvme_dev, ctrl); +} + +static int nvme_pci_setup_prps(struct nvme_dev *dev, + const struct nvme_request *req, + struct nvme_rw_command *cmnd) +{ + int length = req->buffer_len; + const int page_size = dev->ctrl.page_size; + dma_addr_t dma_addr = req->buffer_dma_addr; + u32 offset = dma_addr & (page_size - 1); + u64 prp1 = dma_addr; + __le64 *prp_list; + int i, nprps; + dma_addr_t prp_dma; + + + length -= (page_size - offset); + if (length <= 0) { + prp_dma = 0; + goto done; + } + + dma_addr += (page_size - offset); + + if (length <= page_size) { + prp_dma = dma_addr; + goto done; + } + + nprps = DIV_ROUND_UP(length, page_size); + if (nprps > dev->prp_pool_size) { + dma_free_coherent(dev->prp_pool, dev->prp_dma, + dev->prp_pool_size * sizeof(u64)); + dev->prp_pool_size = nprps; + dev->prp_pool = dma_alloc_coherent(nprps * sizeof(u64), + &dev->prp_dma); + } + + prp_list = dev->prp_pool; + prp_dma = dev->prp_dma; + + i = 0; + for (;;) { + if (i == page_size >> 3) { + __le64 *old_prp_list = prp_list; + prp_list = &prp_list[i]; + prp_dma += page_size; + prp_list[0] = old_prp_list[i - 1]; + old_prp_list[i - 1] = cpu_to_le64(prp_dma); + i = 1; + } + + prp_list[i++] = cpu_to_le64(dma_addr); + dma_addr += page_size; + length -= page_size; + if (length <= 0) + break; + } + +done: + cmnd->dptr.prp1 = cpu_to_le64(prp1); + cmnd->dptr.prp2 = cpu_to_le64(prp_dma); + + return 0; +} + +static int nvme_map_data(struct nvme_dev *dev, struct nvme_request *req) +{ + if (!req->buffer || !req->buffer_len) + return 0; + + req->buffer_dma_addr = dma_map_single(dev->dev, req->buffer, + req->buffer_len, req->dma_dir); + if (dma_mapping_error(dev->dev, req->buffer_dma_addr)) + return -EFAULT; + + return nvme_pci_setup_prps(dev, req, &req->cmd->rw); +} + +static void nvme_unmap_data(struct nvme_dev *dev, struct nvme_request *req) +{ + if (!req->buffer || !req->buffer_len) + return; + + dma_unmap_single(dev->dev, req->buffer_dma_addr, req->buffer_len, + req->dma_dir); +} + +static int nvme_alloc_queue(struct nvme_dev *dev, int qid, int depth) +{ + struct nvme_queue *nvmeq = &dev->queues[qid]; + + if (dev->ctrl.queue_count > qid) + return 0; + + nvmeq->cqes = dma_alloc_coherent(CQ_SIZE(depth), + &nvmeq->cq_dma_addr); + if (!nvmeq->cqes) + goto free_nvmeq; + + nvmeq->sq_cmds = dma_alloc_coherent(SQ_SIZE(depth), + &nvmeq->sq_dma_addr); + if (!nvmeq->sq_cmds) + goto free_cqdma; + + nvmeq->dev = dev; + nvmeq->cq_head = 0; + nvmeq->cq_phase = 1; + nvmeq->q_db = &dev->dbs[qid * 2 * dev->db_stride]; + nvmeq->q_depth = depth; + nvmeq->qid = qid; + dev->ctrl.queue_count++; + + return 0; + + free_cqdma: + dma_free_coherent((void *)nvmeq->cqes, nvmeq->cq_dma_addr, + CQ_SIZE(depth)); + free_nvmeq: + return -ENOMEM; +} + +static int adapter_delete_queue(struct nvme_dev *dev, u8 opcode, u16 id) +{ + struct nvme_command c; + + memset(&c, 0, sizeof(c)); + c.delete_queue.opcode = opcode; + c.delete_queue.qid = cpu_to_le16(id); + + return nvme_submit_sync_cmd(&dev->ctrl, &c, NULL, 0); +} + +static int adapter_alloc_cq(struct nvme_dev *dev, u16 qid, + struct nvme_queue *nvmeq, s16 vector) +{ + struct nvme_command c; + int flags = NVME_QUEUE_PHYS_CONTIG | NVME_CQ_IRQ_ENABLED; + + /* + * Note: we (ab)use the fact that the prp fields survive if no data + * is attached to the request. + */ + memset(&c, 0, sizeof(c)); + c.create_cq.opcode = nvme_admin_create_cq; + c.create_cq.prp1 = cpu_to_le64(nvmeq->cq_dma_addr); + c.create_cq.cqid = cpu_to_le16(qid); + c.create_cq.qsize = cpu_to_le16(nvmeq->q_depth - 1); + c.create_cq.cq_flags = cpu_to_le16(flags); + c.create_cq.irq_vector = cpu_to_le16(vector); + + return nvme_submit_sync_cmd(&dev->ctrl, &c, NULL, 0); +} + +static int adapter_alloc_sq(struct nvme_dev *dev, u16 qid, + struct nvme_queue *nvmeq) +{ + struct nvme_command c; + int flags = NVME_QUEUE_PHYS_CONTIG; + + /* + * Note: we (ab)use the fact that the prp fields survive if no data + * is attached to the request. + */ + memset(&c, 0, sizeof(c)); + c.create_sq.opcode = nvme_admin_create_sq; + c.create_sq.prp1 = cpu_to_le64(nvmeq->sq_dma_addr); + c.create_sq.sqid = cpu_to_le16(qid); + c.create_sq.qsize = cpu_to_le16(nvmeq->q_depth - 1); + c.create_sq.sq_flags = cpu_to_le16(flags); + c.create_sq.cqid = cpu_to_le16(qid); + + return nvme_submit_sync_cmd(&dev->ctrl, &c, NULL, 0); +} + +static int adapter_delete_cq(struct nvme_dev *dev, u16 cqid) +{ + return adapter_delete_queue(dev, nvme_admin_delete_cq, cqid); +} + +static void nvme_init_queue(struct nvme_queue *nvmeq, u16 qid) +{ + struct nvme_dev *dev = nvmeq->dev; + + nvmeq->sq_tail = 0; + nvmeq->cq_head = 0; + nvmeq->cq_phase = 1; + nvmeq->q_db = &dev->dbs[qid * 2 * dev->db_stride]; + dev->online_queues++; +} + +static int nvme_create_queue(struct nvme_queue *nvmeq, int qid) +{ + struct nvme_dev *dev = nvmeq->dev; + int result; + s16 vector; + + vector = 0; + result = adapter_alloc_cq(dev, qid, nvmeq, vector); + if (result) + return result; + + result = adapter_alloc_sq(dev, qid, nvmeq); + if (result < 0) + return result; + else if (result) + goto release_cq; + + nvme_init_queue(nvmeq, qid); + + return result; + +release_cq: + adapter_delete_cq(dev, qid); + return result; +} + +/** + * nvme_submit_cmd() - Copy a command into a queue and ring the doorbell + * @nvmeq: The queue to use + * @cmd: The command to send + */ +static void nvme_submit_cmd(struct nvme_queue *nvmeq, struct nvme_command *cmd) +{ + memcpy(&nvmeq->sq_cmds[nvmeq->sq_tail], cmd, sizeof(*cmd)); + + if (++nvmeq->sq_tail == nvmeq->q_depth) + nvmeq->sq_tail = 0; + writel(nvmeq->sq_tail, nvmeq->q_db); +} + +/* We read the CQE phase first to check if the rest of the entry is valid */ +static inline bool nvme_cqe_pending(struct nvme_queue *nvmeq) +{ + return (le16_to_cpu(nvmeq->cqes[nvmeq->cq_head].status) & 1) == + nvmeq->cq_phase; +} + +static inline void nvme_ring_cq_doorbell(struct nvme_queue *nvmeq) +{ + u16 head = nvmeq->cq_head; + + writel(head, nvmeq->q_db + nvmeq->dev->db_stride); +} + +static inline void nvme_handle_cqe(struct nvme_queue *nvmeq, u16 idx) +{ + volatile struct nvme_completion *cqe = &nvmeq->cqes[idx]; + struct nvme_request *req = nvmeq->req; + + if (unlikely(cqe->command_id >= nvmeq->q_depth)) { + dev_warn(nvmeq->dev->ctrl.dev, + "invalid id %d completed on queue %d\n", + cqe->command_id, le16_to_cpu(cqe->sq_id)); + return; + } + + if (WARN_ON(cqe->command_id != req->cmd->common.command_id)) + return; + + nvme_end_request(req, cqe->status, cqe->result); +} + +static void nvme_complete_cqes(struct nvme_queue *nvmeq, u16 start, u16 end) +{ + while (start != end) { + nvme_handle_cqe(nvmeq, start); + if (++start == nvmeq->q_depth) + start = 0; + } +} + +static inline void nvme_update_cq_head(struct nvme_queue *nvmeq) +{ + if (++nvmeq->cq_head == nvmeq->q_depth) { + nvmeq->cq_head = 0; + nvmeq->cq_phase = !nvmeq->cq_phase; + } +} + +static inline bool nvme_process_cq(struct nvme_queue *nvmeq, u16 *start, + u16 *end, int tag) +{ + bool found = false; + + *start = nvmeq->cq_head; + while (!found && nvme_cqe_pending(nvmeq)) { + if (nvmeq->cqes[nvmeq->cq_head].command_id == tag) + found = true; + nvme_update_cq_head(nvmeq); + } + *end = nvmeq->cq_head; + + if (*start != *end) + nvme_ring_cq_doorbell(nvmeq); + return found; +} + +static bool nvme_poll(struct nvme_queue *nvmeq, unsigned int tag) +{ + u16 start, end; + bool found; + + if (!nvme_cqe_pending(nvmeq)) + return false; + + found = nvme_process_cq(nvmeq, &start, &end, tag); + + nvme_complete_cqes(nvmeq, start, end); + return found; +} + +static int nvme_pci_submit_sync_cmd(struct nvme_ctrl *ctrl, + struct nvme_command *cmd, + union nvme_result *result, + void *buffer, + unsigned int buffer_len, + unsigned timeout, int qid) +{ + struct nvme_dev *dev = to_nvme_dev(ctrl); + struct nvme_queue *nvmeq = &dev->queues[qid]; + struct nvme_request req = { }; + const u16 tag = nvmeq->counter++ & (nvmeq->q_depth - 1); + enum dma_data_direction dma_dir; + int ret; + + switch (qid) { + case NVME_QID_ADMIN: + switch (cmd->common.opcode) { + case nvme_admin_create_sq: + case nvme_admin_create_cq: + case nvme_admin_delete_sq: + case nvme_admin_delete_cq: + case nvme_admin_set_features: + dma_dir = DMA_TO_DEVICE; + break; + case nvme_admin_identify: + dma_dir = DMA_FROM_DEVICE; + break; + default: + return -EINVAL; + } + break; + case NVME_QID_IO: + switch (cmd->rw.opcode) { + case nvme_cmd_write: + dma_dir = DMA_TO_DEVICE; + break; + case nvme_cmd_read: + dma_dir = DMA_FROM_DEVICE; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + cmd->common.command_id = tag; + + timeout = timeout ?: ADMIN_TIMEOUT; + + req.cmd = cmd; + req.buffer = buffer; + req.buffer_len = buffer_len; + req.dma_dir = dma_dir; + + ret = nvme_map_data(dev, &req); + if (ret) { + dev_err(dev->dev, "Failed to map request data\n"); + return ret; + } + + nvme_submit_cmd(nvmeq, cmd); + + nvmeq->req = &req; + ret = wait_on_timeout(timeout, nvme_poll(nvmeq, tag)); + nvmeq->req = NULL; + + nvme_unmap_data(dev, &req); + + if (result) + *result = req.result; + + return ret ?: req.status; +} + +static int nvme_pci_configure_admin_queue(struct nvme_dev *dev) +{ + int result; + u32 aqa; + struct nvme_queue *nvmeq; + + dev->subsystem = readl(dev->bar + NVME_REG_VS) >= NVME_VS(1, 1, 0) ? + NVME_CAP_NSSRC(dev->ctrl.cap) : 0; + + if (dev->subsystem && + (readl(dev->bar + NVME_REG_CSTS) & NVME_CSTS_NSSRO)) + writel(NVME_CSTS_NSSRO, dev->bar + NVME_REG_CSTS); + + result = nvme_disable_ctrl(&dev->ctrl, dev->ctrl.cap); + if (result < 0) + return result; + + result = nvme_alloc_queue(dev, NVME_QID_ADMIN, NVME_AQ_DEPTH); + if (result) + return result; + + nvmeq = &dev->queues[NVME_QID_ADMIN]; + aqa = nvmeq->q_depth - 1; + aqa |= aqa << 16; + + writel(aqa, dev->bar + NVME_REG_AQA); + writeq(nvmeq->sq_dma_addr, dev->bar + NVME_REG_ASQ); + writeq(nvmeq->cq_dma_addr, dev->bar + NVME_REG_ACQ); + + result = nvme_enable_ctrl(&dev->ctrl, dev->ctrl.cap); + if (result) + return result; + + nvme_init_queue(nvmeq, NVME_QID_ADMIN); + + return result; +} + +static int nvme_create_io_queues(struct nvme_dev *dev) +{ + unsigned i, max; + int ret = 0; + + for (i = dev->ctrl.queue_count; i <= dev->max_qid; i++) { + if (nvme_alloc_queue(dev, i, dev->q_depth)) { + ret = -ENOMEM; + break; + } + } + + max = min(dev->max_qid, dev->ctrl.queue_count - 1); + for (i = dev->online_queues; i <= max; i++) { + ret = nvme_create_queue(&dev->queues[i], i); + if (ret) + break; + } + + /* + * Ignore failing Create SQ/CQ commands, we can continue with less + * than the desired amount of queues, and even a controller without + * I/O queues can still be used to issue admin commands. This might + * be useful to upgrade a buggy firmware for example. + */ + return ret >= 0 ? 0 : ret; +} + +static int nvme_setup_io_queues(struct nvme_dev *dev) +{ + int result, nr_io_queues; + + nr_io_queues = NVME_QID_NUM - 1; + result = nvme_set_queue_count(&dev->ctrl, &nr_io_queues); + if (result < 0) + return result; + + dev->max_qid = nr_io_queues; + + return nvme_create_io_queues(dev); +} + +static int nvme_pci_enable(struct nvme_dev *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev->dev); + + if (pci_enable_device(pdev)) + return -ENOMEM; + + pci_set_master(pdev); + + if (readl(dev->bar + NVME_REG_CSTS) == -1) + return -ENODEV; + + dev->ctrl.cap = readq(dev->bar + NVME_REG_CAP); + + dev->q_depth = min_t(int, NVME_CAP_MQES(dev->ctrl.cap) + 1, + io_queue_depth); + dev->db_stride = 1 << NVME_CAP_STRIDE(dev->ctrl.cap); + dev->dbs = dev->bar + 4096; + + return 0; +} + +static void nvme_reset_work(struct nvme_dev *dev) +{ + int result = -ENODEV; + + result = nvme_pci_enable(dev); + if (result) + goto out; + + result = nvme_pci_configure_admin_queue(dev); + if (result) + goto out; + + /* + * Limit the max command size to prevent iod->sg allocations going + * over a single page. + */ + dev->ctrl.max_hw_sectors = NVME_MAX_KB_SZ << 1; + + result = nvme_init_identify(&dev->ctrl); + if (result) + goto out; + + result = nvme_setup_io_queues(dev); + if (result) { + dev_err(dev->ctrl.dev, "IO queues not created\n"); + goto out; + } + + nvme_start_ctrl(&dev->ctrl); +out: + return; +} + +static int nvme_pci_reg_read32(struct nvme_ctrl *ctrl, u32 off, u32 *val) +{ + *val = readl(to_nvme_dev(ctrl)->bar + off); + return 0; +} + +static int nvme_pci_reg_write32(struct nvme_ctrl *ctrl, u32 off, u32 val) +{ + writel(val, to_nvme_dev(ctrl)->bar + off); + return 0; +} + +static int nvme_pci_reg_read64(struct nvme_ctrl *ctrl, u32 off, u64 *val) +{ + *val = readq(to_nvme_dev(ctrl)->bar + off); + return 0; +} + +static const struct nvme_ctrl_ops nvme_pci_ctrl_ops = { + .reg_read32 = nvme_pci_reg_read32, + .reg_write32 = nvme_pci_reg_write32, + .reg_read64 = nvme_pci_reg_read64, + .submit_sync_cmd = nvme_pci_submit_sync_cmd, +}; + +static void nvme_dev_map(struct nvme_dev *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev->dev); + + dev->bar = pci_iomap(pdev, 0); +} + +static void nvme_delete_queue(struct nvme_queue *nvmeq, u8 opcode) +{ + int ret; + ret = adapter_delete_queue(nvmeq->dev, opcode, nvmeq->qid); + if (ret < 0) + dev_err(nvmeq->dev->dev, "%s: %s\n", __func__, + strerror(-ret)); + else if (ret) + dev_err(nvmeq->dev->dev, + "%s: status code type: %xh, status code %02xh\n", + __func__, (ret >> 8) & 0xf, ret & 0xff); +} + +static void nvme_disable_io_queues(struct nvme_dev *dev) +{ + int i, queues = dev->online_queues - 1; + + for (i = queues; i > 0; i--) { + nvme_delete_queue(&dev->queues[i], nvme_admin_delete_sq); + nvme_delete_queue(&dev->queues[i], nvme_admin_delete_cq); + } +} + +static void nvme_disable_admin_queue(struct nvme_dev *dev) +{ + struct nvme_queue *nvmeq = &dev->queues[0]; + u16 start, end; + + nvme_shutdown_ctrl(&dev->ctrl); + nvme_process_cq(nvmeq, &start, &end, -1); + nvme_complete_cqes(nvmeq, start, end); +} + +static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct nvme_dev *dev; + int result; + + dev = xzalloc(sizeof(*dev)); + dev->dev = &pdev->dev; + pdev->dev.priv = dev; + + nvme_dev_map(dev); + result = nvme_init_ctrl(&dev->ctrl, &pdev->dev, &nvme_pci_ctrl_ops); + if (result) + return result; + + nvme_reset_work(dev); + + return 0; +} + +static void nvme_remove(struct pci_dev *pdev) +{ + struct nvme_dev *dev = pdev->dev.priv; + bool dead = true; + + u32 csts = readl(dev->bar + NVME_REG_CSTS); + + dead = !!((csts & NVME_CSTS_CFS) || !(csts & NVME_CSTS_RDY)); + + if (!dead && dev->ctrl.queue_count > 0) { + nvme_disable_io_queues(dev); + nvme_disable_admin_queue(dev); + } +} + +static const struct pci_device_id nvme_id_table[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, PCI_ANY_ID) }, + { 0, }, +}; + +static struct pci_driver nvme_driver = { + .name = "nvme", + .id_table = nvme_id_table, + .probe = nvme_probe, + .remove = nvme_remove, +}; +device_pci_driver(nvme_driver); diff --git a/drivers/pci/pci-imx6.c b/drivers/pci/pci-imx6.c index 138b4ca8b3..85307bad3e 100644 --- a/drivers/pci/pci-imx6.c +++ b/drivers/pci/pci-imx6.c @@ -65,6 +65,7 @@ struct imx6_pcie { struct clk *pcie_bus; struct clk *pcie_phy; struct clk *pcie; + struct clk *pcie_aux; void __iomem *iomuxc_gpr; u32 controller_id; struct reset_control *pciephy_reset; @@ -299,8 +300,10 @@ static unsigned int imx6_pcie_grp_offset(const struct imx6_pcie *imx6_pcie) static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) { + struct device_d *dev = imx6_pcie->pci->dev; u32 gpr1, gpr1x; unsigned int offset; + int ret; switch (imx6_pcie->drvdata->variant) { case IMX6QP: @@ -323,6 +326,12 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) case IMX7D: break; case IMX8MQ: + ret = clk_enable(imx6_pcie->pcie_aux); + if (ret) { + dev_err(dev, "unable to enable pcie_aux clock\n"); + return ret; + } + offset = imx6_pcie_grp_offset(imx6_pcie); /* * Set the over ride low and enabled @@ -742,6 +751,13 @@ static int imx6_pcie_probe(struct device_d *dev) if (iores->start == IMX8MQ_PCIE2_BASE_ADDR) imx6_pcie->controller_id = 1; + imx6_pcie->pcie_aux = clk_get(dev, "pcie_aux"); + if (IS_ERR(imx6_pcie->pcie_aux)) { + dev_err(dev, + "pcie_aux clock source missing or invalid\n"); + return PTR_ERR(imx6_pcie->pcie_aux); + } + goto imx7d_init; case IMX7D: imx6_pcie->iomuxc_gpr = IOMEM(MX7_IOMUXC_GPR_BASE_ADDR); diff --git a/drivers/pci/pcie-designware.c b/drivers/pci/pcie-designware.c index aaea316e90..c6d19559f4 100644 --- a/drivers/pci/pcie-designware.c +++ b/drivers/pci/pcie-designware.c @@ -195,7 +195,7 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci) /* Check if the link is up or not */ for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { if (dw_pcie_link_up(pci)) { - dev_info(pci->dev, "Link up\n"); + dev_dbg(pci->dev, "Link up\n"); return 0; } udelay(LINK_WAIT_USLEEP_MAX); diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index b0c8b9bf0e..b5cefb2ff3 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -22,4 +22,6 @@ config USB_NOP_XCEIV built-in with usb ip or which are autonomous and doesn't require any phy programming such as ISP1x04 etc. +source "drivers/phy/freescale/Kconfig" + endif diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 8fc85953b3..179c55e60a 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_GENERIC_PHY) += phy-core.o obj-$(CONFIG_USB_NOP_XCEIV) += usb-nop-xceiv.o +obj-y += freescale/ diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig new file mode 100644 index 0000000000..8e56dd7e79 --- /dev/null +++ b/drivers/phy/freescale/Kconfig @@ -0,0 +1,4 @@ +config PHY_FSL_IMX8MQ_USB + bool "Freescale i.MX8M USB3 PHY" + default SOC_IMX8MQ + diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile new file mode 100644 index 0000000000..dc2b3f1f2f --- /dev/null +++ b/drivers/phy/freescale/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_PHY_FSL_IMX8MQ_USB) += phy-fsl-imx8mq-usb.o diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c new file mode 100644 index 0000000000..1aef2b3004 --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (c) 2017 NXP. */ + +#include <common.h> +#include <init.h> +#include <io.h> +#include <of.h> +#include <errno.h> +#include <driver.h> +#include <malloc.h> +#include <usb/phy.h> +#include <linux/phy/phy.h> +#include <linux/clk.h> +#include <linux/err.h> + + +#define PHY_CTRL0 0x0 +#define PHY_CTRL0_REF_SSP_EN BIT(2) + +#define PHY_CTRL1 0x4 +#define PHY_CTRL1_RESET BIT(0) +#define PHY_CTRL1_COMMONONN BIT(1) +#define PHY_CTRL1_ATERESET BIT(3) +#define PHY_CTRL1_VDATSRCENB0 BIT(19) +#define PHY_CTRL1_VDATDETENB0 BIT(20) + +#define PHY_CTRL2 0x8 +#define PHY_CTRL2_TXENABLEN0 BIT(8) + +struct imx8mq_usb_phy { + struct phy *phy; + struct clk *clk; + void __iomem *base; +}; + +static int imx8mq_usb_phy_init(struct phy *phy) +{ + struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy); + u32 value; + + value = readl(imx_phy->base + PHY_CTRL1); + value &= ~(PHY_CTRL1_VDATSRCENB0 | PHY_CTRL1_VDATDETENB0 | + PHY_CTRL1_COMMONONN); + value |= PHY_CTRL1_RESET | PHY_CTRL1_ATERESET; + writel(value, imx_phy->base + PHY_CTRL1); + + value = readl(imx_phy->base + PHY_CTRL0); + value |= PHY_CTRL0_REF_SSP_EN; + writel(value, imx_phy->base + PHY_CTRL0); + + value = readl(imx_phy->base + PHY_CTRL2); + value |= PHY_CTRL2_TXENABLEN0; + writel(value, imx_phy->base + PHY_CTRL2); + + value = readl(imx_phy->base + PHY_CTRL1); + value &= ~(PHY_CTRL1_RESET | PHY_CTRL1_ATERESET); + writel(value, imx_phy->base + PHY_CTRL1); + + return 0; +} + +static int imx8mq_phy_power_on(struct phy *phy) +{ + struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy); + + return clk_enable(imx_phy->clk); +} + +static int imx8mq_phy_power_off(struct phy *phy) +{ + struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy); + + clk_disable(imx_phy->clk); + + return 0; +} + +static struct phy_ops imx8mq_usb_phy_ops = { + .init = imx8mq_usb_phy_init, + .power_on = imx8mq_phy_power_on, + .power_off = imx8mq_phy_power_off, +}; + +static struct phy *imx8mq_usb_phy_xlate(struct device_d *dev, + struct of_phandle_args *args) +{ + struct imx8mq_usb_phy *imx_phy = dev->priv; + + return imx_phy->phy; +} + +static int imx8mq_usb_phy_probe(struct device_d *dev) +{ + struct phy_provider *phy_provider; + struct imx8mq_usb_phy *imx_phy; + + imx_phy = xzalloc(sizeof(*imx_phy)); + + dev->priv = imx_phy; + + imx_phy->clk = clk_get(dev, "phy"); + if (IS_ERR(imx_phy->clk)) + return PTR_ERR(imx_phy->clk); + + imx_phy->base = dev_get_mem_region(dev, 0); + if (IS_ERR(imx_phy->base)) + return PTR_ERR(imx_phy->base); + + imx_phy->phy = phy_create(dev, NULL, &imx8mq_usb_phy_ops, NULL); + if (IS_ERR(imx_phy->phy)) + return PTR_ERR(imx_phy->phy); + + phy_set_drvdata(imx_phy->phy, imx_phy); + + phy_provider = of_phy_provider_register(dev, imx8mq_usb_phy_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id imx8mq_usb_phy_of_match[] = { + {.compatible = "fsl,imx8mq-usb-phy",}, + { }, +}; + +static struct driver_d imx8mq_usb_phy_driver = { + .name = "imx8mq-usb-phy", + .probe = imx8mq_usb_phy_probe, + .of_compatible = DRV_OF_COMPAT(imx8mq_usb_phy_of_match), +}; +device_platform_driver(imx8mq_usb_phy_driver); diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 9d6288fc07..066a887a22 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -227,7 +227,7 @@ static struct phy_provider *of_phy_provider_lookup(struct device_node *node) return phy_provider; } - return ERR_PTR(-ENODEV); + return ERR_PTR(-EPROBE_DEFER); } /** @@ -254,7 +254,7 @@ static struct phy *_of_phy_get(struct device_node *np, int index) phy_provider = of_phy_provider_lookup(args.np); if (IS_ERR(phy_provider)) { - return ERR_PTR(-ENODEV); + return ERR_CAST(phy_provider); } return phy_provider->of_xlate(phy_provider->dev, &args); diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 8520a2fd9b..8ff3d18d4a 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -9,12 +9,16 @@ if USB_HOST source drivers/usb/imx/Kconfig +source "drivers/usb/dwc3/Kconfig" + source drivers/usb/host/Kconfig source drivers/usb/otg/Kconfig source drivers/usb/storage/Kconfig +source "drivers/usb/misc/Kconfig" + endif source drivers/usb/gadget/Kconfig diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 047f184800..9e98099502 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -1,8 +1,10 @@ obj-$(CONFIG_USB) += core/ obj-$(CONFIG_USB_IMX_CHIPIDEA) += imx/ +obj-$(CONFIG_USB_DWC3) += dwc3/ obj-$(CONFIG_USB_MUSB) += musb/ obj-$(CONFIG_USB_GADGET) += gadget/ obj-$(CONFIG_USB_STORAGE) += storage/ obj-y += host/ obj-y += otg/ +obj-$(CONFIG_USB) += misc/ diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig new file mode 100644 index 0000000000..c91fd36a95 --- /dev/null +++ b/drivers/usb/dwc3/Kconfig @@ -0,0 +1,22 @@ +config USB_DWC3 + tristate "DesignWare USB3 DRD Core Support" + depends on USB && HAS_DMA + select USB_XHCI + select USB_DWC3_HOST # Remove this once we support more + # than USB host + help + Say Y or M here if your system has a Dual Role SuperSpeed + USB controller based on the DesignWare USB3 IP Core. + + If you choose to build this driver is a dynamically linked + module, the module will be called dwc3.ko. + +if USB_DWC3 + +config USB_DWC3_HOST + bool "Host only mode" + help + Select this when you want to use DWC3 in host mode only, + thereby the gadget feature will be regressed. + +endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile new file mode 100644 index 0000000000..d43b23eb2d --- /dev/null +++ b/drivers/usb/dwc3/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_USB_DWC3) += dwc3.o + +dwc3-y := core.o + +ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST)),) + dwc3-y += host.o +endif + diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c new file mode 100644 index 0000000000..2e7031a348 --- /dev/null +++ b/drivers/usb/dwc3/core.c @@ -0,0 +1,740 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * core.c - DesignWare USB3 DRD Controller Core file + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi <balbi@ti.com>, + * Sebastian Andrzej Siewior <bigeasy@linutronix.de> + */ + +#include <common.h> +#include <linux/clk.h> +#include <linux/phy/phy.h> +#include <driver.h> +#include <init.h> + +#include "core.h" +#include "io.h" + + +#define DWC3_DEFAULT_AUTOSUSPEND_DELAY 5000 /* ms */ + +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG)); + reg |= DWC3_GCTL_PRTCAPDIR(mode); + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + + dwc->current_dr_role = mode; +} + +/** + * dwc3_core_soft_reset - Issues core soft reset and PHY reset + * @dwc: pointer to our context structure + */ +static int dwc3_core_soft_reset(struct dwc3 *dwc) +{ + u32 reg; + int retries = 1000; + int ret; + + ret = phy_init(dwc->usb2_generic_phy); + if (ret < 0) + return ret; + + ret = phy_init(dwc->usb3_generic_phy); + if (ret < 0) { + phy_exit(dwc->usb2_generic_phy); + return ret; + } + + /* + * We're resetting only the device side because, if we're in host mode, + * XHCI driver will reset the host block. If dwc3 was configured for + * host-only mode, then we can return early. + */ + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST) + return 0; + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg |= DWC3_DCTL_CSFTRST; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + do { + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (!(reg & DWC3_DCTL_CSFTRST)) + goto done; + + udelay(1); + } while (--retries); + + phy_exit(dwc->usb3_generic_phy); + phy_exit(dwc->usb2_generic_phy); + + return -ETIMEDOUT; + +done: + /* + * For DWC_usb31 controller, once DWC3_DCTL_CSFTRST bit is cleared, + * we must wait at least 50ms before accessing the PHY domain + * (synchronization delay). DWC_usb31 programming guide section 1.3.2. + */ + if (dwc3_is_usb31(dwc)) + mdelay(50); + + return 0; +} + +static const struct clk_bulk_data dwc3_core_clks[] = { + { .id = "ref" }, + { .id = "bus_early" }, + { .id = "suspend" }, +}; + +/* + * dwc3_frame_length_adjustment - Adjusts frame length if required + * @dwc3: Pointer to our controller context structure + */ +static void dwc3_frame_length_adjustment(struct dwc3 *dwc) +{ + u32 reg; + u32 dft; + + if (dwc->revision < DWC3_REVISION_250A) + return; + + if (dwc->fladj == 0) + return; + + reg = dwc3_readl(dwc->regs, DWC3_GFLADJ); + dft = reg & DWC3_GFLADJ_30MHZ_MASK; + if (!WARN(dft == dwc->fladj, + "request value same as default, ignoring\n")) { + reg &= ~DWC3_GFLADJ_30MHZ_MASK; + reg |= DWC3_GFLADJ_30MHZ_SDBND_SEL | dwc->fladj; + dwc3_writel(dwc->regs, DWC3_GFLADJ, reg); + } +} + +static void dwc3_core_num_eps(struct dwc3 *dwc) +{ + struct dwc3_hwparams *parms = &dwc->hwparams; + + dwc->num_eps = DWC3_NUM_EPS(parms); +} + +static void dwc3_cache_hwparams(struct dwc3 *dwc) +{ + struct dwc3_hwparams *parms = &dwc->hwparams; + + parms->hwparams0 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS0); + parms->hwparams1 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS1); + parms->hwparams2 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS2); + parms->hwparams3 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS3); + parms->hwparams4 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS4); + parms->hwparams5 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS5); + parms->hwparams6 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6); + parms->hwparams7 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS7); + parms->hwparams8 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS8); +} + +/** + * dwc3_phy_setup - Configure USB PHY Interface of DWC3 Core + * @dwc: Pointer to our controller context structure + * + * Returns 0 on success. The USB PHY interfaces are configured but not + * initialized. The PHY interfaces and the PHYs get initialized together with + * the core in dwc3_core_init. + */ +static int dwc3_phy_setup(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); + + /* + * Make sure UX_EXIT_PX is cleared as that causes issues with some + * PHYs. Also, this bit is not supposed to be used in normal operation. + */ + reg &= ~DWC3_GUSB3PIPECTL_UX_EXIT_PX; + + /* + * Above 1.94a, it is recommended to set DWC3_GUSB3PIPECTL_SUSPHY + * to '0' during coreConsultant configuration. So default value + * will be '0' when the core is reset. Application needs to set it + * to '1' after the core initialization is completed. + */ + if (dwc->revision > DWC3_REVISION_194A) + reg |= DWC3_GUSB3PIPECTL_SUSPHY; + + if (dwc->u2ss_inp3_quirk) + reg |= DWC3_GUSB3PIPECTL_U2SSINP3OK; + + if (dwc->dis_rxdet_inp3_quirk) + reg |= DWC3_GUSB3PIPECTL_DISRXDETINP3; + + if (dwc->req_p1p2p3_quirk) + reg |= DWC3_GUSB3PIPECTL_REQP1P2P3; + + if (dwc->del_p1p2p3_quirk) + reg |= DWC3_GUSB3PIPECTL_DEP1P2P3_EN; + + if (dwc->del_phy_power_chg_quirk) + reg |= DWC3_GUSB3PIPECTL_DEPOCHANGE; + + if (dwc->lfps_filter_quirk) + reg |= DWC3_GUSB3PIPECTL_LFPSFILT; + + if (dwc->rx_detect_poll_quirk) + reg |= DWC3_GUSB3PIPECTL_RX_DETOPOLL; + + if (dwc->tx_de_emphasis_quirk) + reg |= DWC3_GUSB3PIPECTL_TX_DEEPH(dwc->tx_de_emphasis); + + if (dwc->dis_u3_susphy_quirk) + reg &= ~DWC3_GUSB3PIPECTL_SUSPHY; + + if (dwc->dis_del_phy_power_chg_quirk) + reg &= ~DWC3_GUSB3PIPECTL_DEPOCHANGE; + + dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); + + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + + /* Select the HS PHY interface */ + switch (DWC3_GHWPARAMS3_HSPHY_IFC(dwc->hwparams.hwparams3)) { + case DWC3_GHWPARAMS3_HSPHY_IFC_UTMI_ULPI: + if (dwc->hsphy_interface && + !strncmp(dwc->hsphy_interface, "utmi", 4)) { + reg &= ~DWC3_GUSB2PHYCFG_ULPI_UTMI; + break; + } else if (dwc->hsphy_interface && + !strncmp(dwc->hsphy_interface, "ulpi", 4)) { + reg |= DWC3_GUSB2PHYCFG_ULPI_UTMI; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + } else { + /* Relying on default value. */ + if (!(reg & DWC3_GUSB2PHYCFG_ULPI_UTMI)) + break; + } + /* FALLTHROUGH */ + case DWC3_GHWPARAMS3_HSPHY_IFC_ULPI: + /* FALLTHROUGH */ + default: + break; + } + + /* + * Above 1.94a, it is recommended to set DWC3_GUSB2PHYCFG_SUSPHY to + * '0' during coreConsultant configuration. So default value will + * be '0' when the core is reset. Application needs to set it to + * '1' after the core initialization is completed. + */ + if (dwc->revision > DWC3_REVISION_194A) + reg |= DWC3_GUSB2PHYCFG_SUSPHY; + + if (dwc->dis_u2_susphy_quirk) + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + + if (dwc->dis_enblslpm_quirk) + reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM; + else + reg |= DWC3_GUSB2PHYCFG_ENBLSLPM; + + if (dwc->dis_u2_freeclk_exists_quirk) + reg &= ~DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS; + + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + + return 0; +} + +static void dwc3_core_exit(struct dwc3 *dwc) +{ + phy_exit(dwc->usb2_generic_phy); + phy_exit(dwc->usb3_generic_phy); + + phy_power_off(dwc->usb2_generic_phy); + phy_power_off(dwc->usb3_generic_phy); + clk_bulk_disable(dwc->num_clks, dwc->clks); +} + +static bool dwc3_core_is_valid(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_GSNPSID); + + /* This should read as U3 followed by revision number */ + if ((reg & DWC3_GSNPSID_MASK) == 0x55330000) { + /* Detected DWC_usb3 IP */ + dwc->revision = reg; + } else if ((reg & DWC3_GSNPSID_MASK) == 0x33310000) { + /* Detected DWC_usb31 IP */ + dwc->revision = dwc3_readl(dwc->regs, DWC3_VER_NUMBER); + dwc->revision |= DWC3_REVISION_IS_DWC31; + dwc->version_type = dwc3_readl(dwc->regs, DWC3_VER_TYPE); + } else { + return false; + } + + return true; +} + +static void dwc3_core_setup_global_control(struct dwc3 *dwc) +{ + u32 hwparams4 = dwc->hwparams.hwparams4; + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~DWC3_GCTL_SCALEDOWN_MASK; + + switch (DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1)) { + case DWC3_GHWPARAMS1_EN_PWROPT_CLK: + /** + * WORKAROUND: DWC3 revisions between 2.10a and 2.50a have an + * issue which would cause xHCI compliance tests to fail. + * + * Because of that we cannot enable clock gating on such + * configurations. + * + * Refers to: + * + * STAR#9000588375: Clock Gating, SOF Issues when ref_clk-Based + * SOF/ITP Mode Used + */ + if ((dwc->dr_mode == USB_DR_MODE_HOST || + dwc->dr_mode == USB_DR_MODE_OTG) && + (dwc->revision >= DWC3_REVISION_210A && + dwc->revision <= DWC3_REVISION_250A)) + reg |= DWC3_GCTL_DSBLCLKGTNG | DWC3_GCTL_SOFITPSYNC; + else + reg &= ~DWC3_GCTL_DSBLCLKGTNG; + break; + case DWC3_GHWPARAMS1_EN_PWROPT_HIB: + /* enable hibernation here */ + dwc->nr_scratch = DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(hwparams4); + + /* + * REVISIT Enabling this bit so that host-mode hibernation + * will work. Device-mode hibernation is not yet implemented. + */ + reg |= DWC3_GCTL_GBLHIBERNATIONEN; + break; + default: + /* nothing */ + break; + } + + /* check if current dwc3 is on simulation board */ + if (dwc->hwparams.hwparams6 & DWC3_GHWPARAMS6_EN_FPGA) { + dev_info(dwc->dev, "Running with FPGA optimizations\n"); + dwc->is_fpga = true; + } + + WARN(dwc->disable_scramble_quirk && !dwc->is_fpga, + "disable_scramble cannot be used on non-FPGA builds\n"); + + if (dwc->disable_scramble_quirk && dwc->is_fpga) + reg |= DWC3_GCTL_DISSCRAMBLE; + else + reg &= ~DWC3_GCTL_DISSCRAMBLE; + + if (dwc->u2exit_lfps_quirk) + reg |= DWC3_GCTL_U2EXIT_LFPS; + + /* + * WORKAROUND: DWC3 revisions <1.90a have a bug + * where the device can fail to connect at SuperSpeed + * and falls back to high-speed mode which causes + * the device to enter a Connect/Disconnect loop + */ + if (dwc->revision < DWC3_REVISION_190A) + reg |= DWC3_GCTL_U2RSTECN; + + dwc3_writel(dwc->regs, DWC3_GCTL, reg); +} + +static int dwc3_core_get_phy(struct dwc3 *dwc); + +/** + * dwc3_core_init - Low-level initialization of DWC3 Core + * @dwc: Pointer to our controller context structure + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_core_init(struct dwc3 *dwc) +{ + u32 reg; + int ret; + + if (!dwc3_core_is_valid(dwc)) { + dev_err(dwc->dev, "this is not a DesignWare USB3 DRD Core\n"); + ret = -ENODEV; + goto err0; + } + + /* + * Write Linux Version Code to our GUID register so it's easy to figure + * out which kernel version a bug was found. + */ + dwc3_writel(dwc->regs, DWC3_GUID, 0xdeadbeef); + + /* Handle USB2.0-only core configuration */ + if (DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3) == + DWC3_GHWPARAMS3_SSPHY_IFC_DIS) { + if (dwc->maximum_speed == USB_SPEED_SUPER) + dwc->maximum_speed = USB_SPEED_HIGH; + } + + ret = dwc3_phy_setup(dwc); + if (ret) + goto err0; + + if (!dwc->phys_ready) { + ret = dwc3_core_get_phy(dwc); + if (ret) + goto err0a; + dwc->phys_ready = true; + } + + ret = dwc3_core_soft_reset(dwc); + if (ret) + goto err0a; + + dwc3_core_setup_global_control(dwc); + dwc3_core_num_eps(dwc); + + /* Adjust Frame Length */ + dwc3_frame_length_adjustment(dwc); + + ret = phy_power_on(dwc->usb2_generic_phy); + if (ret < 0) + goto err2; + + ret = phy_power_on(dwc->usb3_generic_phy); + if (ret < 0) + goto err3; + /* + * ENDXFER polling is available on version 3.10a and later of + * the DWC_usb3 controller. It is NOT available in the + * DWC_usb31 controller. + */ + if (!dwc3_is_usb31(dwc) && dwc->revision >= DWC3_REVISION_310A) { + reg = dwc3_readl(dwc->regs, DWC3_GUCTL2); + reg |= DWC3_GUCTL2_RST_ACTBITLATER; + dwc3_writel(dwc->regs, DWC3_GUCTL2, reg); + } + + if (dwc->revision >= DWC3_REVISION_250A) { + reg = dwc3_readl(dwc->regs, DWC3_GUCTL1); + + /* + * Enable hardware control of sending remote wakeup + * in HS when the device is in the L1 state. + */ + if (dwc->revision >= DWC3_REVISION_290A) + reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW; + + if (dwc->dis_tx_ipgap_linecheck_quirk) + reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS; + + dwc3_writel(dwc->regs, DWC3_GUCTL1, reg); + } + + if (dwc->dr_mode == USB_DR_MODE_HOST || + dwc->dr_mode == USB_DR_MODE_OTG) { + reg = dwc3_readl(dwc->regs, DWC3_GUCTL); + + /* + * Enable Auto retry Feature to make the controller operating in + * Host mode on seeing transaction errors(CRC errors or internal + * overrun scenerios) on IN transfers to reply to the device + * with a non-terminating retry ACK (i.e, an ACK transcation + * packet with Retry=1 & Nump != 0) + */ + reg |= DWC3_GUCTL_HSTINAUTORETRY; + + dwc3_writel(dwc->regs, DWC3_GUCTL, reg); + } + + /* + * Must config both number of packets and max burst settings to enable + * RX and/or TX threshold. + */ + if (dwc3_is_usb31(dwc) && dwc->dr_mode == USB_DR_MODE_HOST) { + u8 rx_thr_num = dwc->rx_thr_num_pkt_prd; + u8 rx_maxburst = dwc->rx_max_burst_prd; + u8 tx_thr_num = dwc->tx_thr_num_pkt_prd; + u8 tx_maxburst = dwc->tx_max_burst_prd; + + if (rx_thr_num && rx_maxburst) { + reg = dwc3_readl(dwc->regs, DWC3_GRXTHRCFG); + reg |= DWC31_RXTHRNUMPKTSEL_PRD; + + reg &= ~DWC31_RXTHRNUMPKT_PRD(~0); + reg |= DWC31_RXTHRNUMPKT_PRD(rx_thr_num); + + reg &= ~DWC31_MAXRXBURSTSIZE_PRD(~0); + reg |= DWC31_MAXRXBURSTSIZE_PRD(rx_maxburst); + + dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, reg); + } + + if (tx_thr_num && tx_maxburst) { + reg = dwc3_readl(dwc->regs, DWC3_GTXTHRCFG); + reg |= DWC31_TXTHRNUMPKTSEL_PRD; + + reg &= ~DWC31_TXTHRNUMPKT_PRD(~0); + reg |= DWC31_TXTHRNUMPKT_PRD(tx_thr_num); + + reg &= ~DWC31_MAXTXBURSTSIZE_PRD(~0); + reg |= DWC31_MAXTXBURSTSIZE_PRD(tx_maxburst); + + dwc3_writel(dwc->regs, DWC3_GTXTHRCFG, reg); + } + } + + return 0; + +/* err4: */ + /* phy_power_off(dwc->usb3_generic_phy); */ +err3: + phy_power_off(dwc->usb2_generic_phy); +err2: +/* err1: */ + phy_exit(dwc->usb2_generic_phy); + phy_exit(dwc->usb3_generic_phy); +err0a: +err0: + return ret; +} + +static int dwc3_core_get_phy(struct dwc3 *dwc) +{ + struct device_d *dev = dwc->dev; + int ret; + + dwc->usb2_generic_phy = phy_get(dev, "usb2-phy"); + if (IS_ERR(dwc->usb2_generic_phy)) { + ret = PTR_ERR(dwc->usb2_generic_phy); + if (ret == -ENOSYS || ret == -ENODEV) { + dev_err(dev, "no usb2 phy configured\n"); + dwc->usb2_generic_phy = NULL; + } else if (ret == -EPROBE_DEFER) { + return ret; + } else { + dev_err(dev, "no usb2 phy configured\n"); + return ret; + } + } + + dwc->usb3_generic_phy = phy_get(dev, "usb3-phy"); + if (IS_ERR(dwc->usb3_generic_phy)) { + ret = PTR_ERR(dwc->usb3_generic_phy); + if (ret == -ENOSYS || ret == -ENODEV) { + dev_err(dev, "no usb2 phy configured\n"); + dwc->usb3_generic_phy = NULL; + } else if (ret == -EPROBE_DEFER) { + return ret; + } else { + dev_err(dev, "no usb3 phy configured\n"); + return ret; + } + } + + return 0; +} + +static int dwc3_core_init_mode(struct dwc3 *dwc) +{ + struct device_d *dev = dwc->dev; + int ret; + + switch (dwc->dr_mode) { + case USB_DR_MODE_HOST: + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST); + + ret = dwc3_host_init(dwc); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to initialize host\n"); + return ret; + } + break; + default: + dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode); + return -EINVAL; + } + + return 0; +} + +static void dwc3_get_properties(struct dwc3 *dwc) +{ + struct device_d *dev = dwc->dev; + u8 lpm_nyet_threshold; + u8 tx_de_emphasis; + u8 hird_threshold; + + /* default to highest possible threshold */ + lpm_nyet_threshold = 0xff; + + /* default to -3.5dB de-emphasis */ + tx_de_emphasis = 1; + + /* + * default to assert utmi_sleep_n and use maximum allowed HIRD + * threshold value of 0b1100 + */ + hird_threshold = 12; + + dwc->maximum_speed = of_usb_get_maximum_speed(dev->device_node, NULL); + dwc->dr_mode = of_usb_get_dr_mode(dev->device_node, NULL); + + dwc->lpm_nyet_threshold = lpm_nyet_threshold; + dwc->tx_de_emphasis = tx_de_emphasis; + + dwc->hird_threshold = hird_threshold + | (dwc->is_utmi_l1_suspend << 4); + + dwc->imod_interval = 0; +} + +/* check whether the core supports IMOD */ +bool dwc3_has_imod(struct dwc3 *dwc) +{ + return ((dwc3_is_usb3(dwc) && + dwc->revision >= DWC3_REVISION_300A) || + (dwc3_is_usb31(dwc) && + dwc->revision >= DWC3_USB31_REVISION_120A)); +} + +static void dwc3_check_params(struct dwc3 *dwc) +{ + struct device_d *dev = dwc->dev; + + /* Check for proper value of imod_interval */ + if (dwc->imod_interval && !dwc3_has_imod(dwc)) { + dev_warn(dwc->dev, "Interrupt moderation not supported\n"); + dwc->imod_interval = 0; + } + + /* + * Workaround for STAR 9000961433 which affects only version + * 3.00a of the DWC_usb3 core. This prevents the controller + * interrupt from being masked while handling events. IMOD + * allows us to work around this issue. Enable it for the + * affected version. + */ + if (!dwc->imod_interval && + (dwc->revision == DWC3_REVISION_300A)) + dwc->imod_interval = 1; + + /* Check the maximum_speed parameter */ + switch (dwc->maximum_speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + break; + default: + dev_err(dev, "invalid maximum_speed parameter %d\n", + dwc->maximum_speed); + /* fall through */ + case USB_SPEED_UNKNOWN: + /* default to superspeed */ + dwc->maximum_speed = USB_SPEED_SUPER; + + /* + * default to superspeed plus if we are capable. + */ + if (dwc3_is_usb31(dwc) && + (DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3) == + DWC3_GHWPARAMS3_SSPHY_IFC_GEN2)) + dwc->maximum_speed = USB_SPEED_SUPER_PLUS; + + break; + } +} + +static int dwc3_probe(struct device_d *dev) +{ + struct dwc3 *dwc; + int ret; + + dwc = xzalloc(sizeof(*dwc)); + dev->priv = dwc; + + dwc->clks = xmemdup(dwc3_core_clks, sizeof(dwc3_core_clks)); + dwc->dev = dev; + dwc->regs = dev_get_mem_region(dwc->dev, 0) + DWC3_GLOBALS_REGS_START; + + dwc3_get_properties(dwc); + + if (dev->device_node) { + dwc->num_clks = ARRAY_SIZE(dwc3_core_clks); + + ret = clk_bulk_get(dev, dwc->num_clks, dwc->clks); + if (ret == -EPROBE_DEFER) + return ret; + /* + * Clocks are optional, but new DT platforms should support all + * clocks as required by the DT-binding. + */ + if (ret) + dwc->num_clks = 0; + } + + ret = clk_bulk_enable(dwc->num_clks, dwc->clks); + if (ret) + return ret; + + dwc3_cache_hwparams(dwc); + + ret = dwc3_core_init(dwc); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to initialize core: %d\n", ret); + return ret; + } + + dwc3_check_params(dwc); + + ret = dwc3_core_init_mode(dwc); + if (ret) + return ret; + + return 0; +} + +static void dwc3_remove(struct device_d *dev) +{ + struct dwc3 *dwc = dev->priv; + + dwc3_core_exit(dwc); + clk_bulk_put(dwc->num_clks, dwc->clks); +} + +static const struct of_device_id of_dwc3_match[] = { + { + .compatible = "snps,dwc3" + }, + { + .compatible = "synopsys,dwc3" + }, + { }, +}; + +static struct driver_d dwc3_driver = { + .probe = dwc3_probe, + .remove = dwc3_remove, + .name = "dwc3", + .of_compatible = DRV_OF_COMPAT(of_dwc3_match), +}; +device_platform_driver(dwc3_driver); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h new file mode 100644 index 0000000000..a404e4cd6a --- /dev/null +++ b/drivers/usb/dwc3/core.h @@ -0,0 +1,1267 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * core.h - DesignWare USB3 DRD Core Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi <balbi@ti.com>, + * Sebastian Andrzej Siewior <bigeasy@linutronix.de> + */ + +#ifndef __DRIVERS_USB_DWC3_CORE_H +#define __DRIVERS_USB_DWC3_CORE_H + +#include <usb/usb.h> + +#define DWC3_MSG_MAX 500 + +/* Global constants */ +#define DWC3_PULL_UP_TIMEOUT 500 /* ms */ +#define DWC3_BOUNCE_SIZE 1024 /* size of a superspeed bulk */ +#define DWC3_EP0_SETUP_SIZE 512 +#define DWC3_ENDPOINTS_NUM 32 +#define DWC3_XHCI_RESOURCES_NUM 2 +#define DWC3_ISOC_MAX_RETRIES 5 + +#define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ +#define DWC3_EVENT_BUFFERS_SIZE 4096 +#define DWC3_EVENT_TYPE_MASK 0xfe + +#define DWC3_EVENT_TYPE_DEV 0 +#define DWC3_EVENT_TYPE_CARKIT 3 +#define DWC3_EVENT_TYPE_I2C 4 + +#define DWC3_DEVICE_EVENT_DISCONNECT 0 +#define DWC3_DEVICE_EVENT_RESET 1 +#define DWC3_DEVICE_EVENT_CONNECT_DONE 2 +#define DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE 3 +#define DWC3_DEVICE_EVENT_WAKEUP 4 +#define DWC3_DEVICE_EVENT_HIBER_REQ 5 +#define DWC3_DEVICE_EVENT_EOPF 6 +#define DWC3_DEVICE_EVENT_SOF 7 +#define DWC3_DEVICE_EVENT_ERRATIC_ERROR 9 +#define DWC3_DEVICE_EVENT_CMD_CMPL 10 +#define DWC3_DEVICE_EVENT_OVERFLOW 11 + +/* Controller's role while using the OTG block */ +#define DWC3_OTG_ROLE_IDLE 0 +#define DWC3_OTG_ROLE_HOST 1 +#define DWC3_OTG_ROLE_DEVICE 2 + +#define DWC3_GEVNTCOUNT_MASK 0xfffc +#define DWC3_GEVNTCOUNT_EHB BIT(31) +#define DWC3_GSNPSID_MASK 0xffff0000 +#define DWC3_GSNPSREV_MASK 0xffff + +/* DWC3 registers memory space boundries */ +#define DWC3_XHCI_REGS_START 0x0 +#define DWC3_XHCI_REGS_END 0x7fff +#define DWC3_GLOBALS_REGS_START 0xc100 +#define DWC3_GLOBALS_REGS_END 0xc6ff +#define DWC3_DEVICE_REGS_START 0xc700 +#define DWC3_DEVICE_REGS_END 0xcbff +#define DWC3_OTG_REGS_START 0xcc00 +#define DWC3_OTG_REGS_END 0xccff + +/* Global Registers */ +#define DWC3_GSBUSCFG0 0xc100 +#define DWC3_GSBUSCFG1 0xc104 +#define DWC3_GTXTHRCFG 0xc108 +#define DWC3_GRXTHRCFG 0xc10c +#define DWC3_GCTL 0xc110 +#define DWC3_GEVTEN 0xc114 +#define DWC3_GSTS 0xc118 +#define DWC3_GUCTL1 0xc11c +#define DWC3_GSNPSID 0xc120 +#define DWC3_GGPIO 0xc124 +#define DWC3_GUID 0xc128 +#define DWC3_GUCTL 0xc12c +#define DWC3_GBUSERRADDR0 0xc130 +#define DWC3_GBUSERRADDR1 0xc134 +#define DWC3_GPRTBIMAP0 0xc138 +#define DWC3_GPRTBIMAP1 0xc13c +#define DWC3_GHWPARAMS0 0xc140 +#define DWC3_GHWPARAMS1 0xc144 +#define DWC3_GHWPARAMS2 0xc148 +#define DWC3_GHWPARAMS3 0xc14c +#define DWC3_GHWPARAMS4 0xc150 +#define DWC3_GHWPARAMS5 0xc154 +#define DWC3_GHWPARAMS6 0xc158 +#define DWC3_GHWPARAMS7 0xc15c +#define DWC3_GDBGFIFOSPACE 0xc160 +#define DWC3_GDBGLTSSM 0xc164 +#define DWC3_GDBGBMU 0xc16c +#define DWC3_GDBGLSPMUX 0xc170 +#define DWC3_GDBGLSP 0xc174 +#define DWC3_GDBGEPINFO0 0xc178 +#define DWC3_GDBGEPINFO1 0xc17c +#define DWC3_GPRTBIMAP_HS0 0xc180 +#define DWC3_GPRTBIMAP_HS1 0xc184 +#define DWC3_GPRTBIMAP_FS0 0xc188 +#define DWC3_GPRTBIMAP_FS1 0xc18c +#define DWC3_GUCTL2 0xc19c + +#define DWC3_VER_NUMBER 0xc1a0 +#define DWC3_VER_TYPE 0xc1a4 + +#define DWC3_GUSB2PHYCFG(n) (0xc200 + ((n) * 0x04)) +#define DWC3_GUSB2I2CCTL(n) (0xc240 + ((n) * 0x04)) + +#define DWC3_GUSB2PHYACC(n) (0xc280 + ((n) * 0x04)) + +#define DWC3_GUSB3PIPECTL(n) (0xc2c0 + ((n) * 0x04)) + +#define DWC3_GTXFIFOSIZ(n) (0xc300 + ((n) * 0x04)) +#define DWC3_GRXFIFOSIZ(n) (0xc380 + ((n) * 0x04)) + +#define DWC3_GEVNTADRLO(n) (0xc400 + ((n) * 0x10)) +#define DWC3_GEVNTADRHI(n) (0xc404 + ((n) * 0x10)) +#define DWC3_GEVNTSIZ(n) (0xc408 + ((n) * 0x10)) +#define DWC3_GEVNTCOUNT(n) (0xc40c + ((n) * 0x10)) + +#define DWC3_GHWPARAMS8 0xc600 +#define DWC3_GFLADJ 0xc630 + +/* Device Registers */ +#define DWC3_DCFG 0xc700 +#define DWC3_DCTL 0xc704 +#define DWC3_DEVTEN 0xc708 +#define DWC3_DSTS 0xc70c +#define DWC3_DGCMDPAR 0xc710 +#define DWC3_DGCMD 0xc714 +#define DWC3_DALEPENA 0xc720 + +#define DWC3_DEP_BASE(n) (0xc800 + ((n) * 0x10)) +#define DWC3_DEPCMDPAR2 0x00 +#define DWC3_DEPCMDPAR1 0x04 +#define DWC3_DEPCMDPAR0 0x08 +#define DWC3_DEPCMD 0x0c + +#define DWC3_DEV_IMOD(n) (0xca00 + ((n) * 0x4)) + +/* OTG Registers */ +#define DWC3_OCFG 0xcc00 +#define DWC3_OCTL 0xcc04 +#define DWC3_OEVT 0xcc08 +#define DWC3_OEVTEN 0xcc0C +#define DWC3_OSTS 0xcc10 + +/* Bit fields */ + +/* Global SoC Bus Configuration INCRx Register 0 */ +#define DWC3_GSBUSCFG0_INCR256BRSTENA (1 << 7) /* INCR256 burst */ +#define DWC3_GSBUSCFG0_INCR128BRSTENA (1 << 6) /* INCR128 burst */ +#define DWC3_GSBUSCFG0_INCR64BRSTENA (1 << 5) /* INCR64 burst */ +#define DWC3_GSBUSCFG0_INCR32BRSTENA (1 << 4) /* INCR32 burst */ +#define DWC3_GSBUSCFG0_INCR16BRSTENA (1 << 3) /* INCR16 burst */ +#define DWC3_GSBUSCFG0_INCR8BRSTENA (1 << 2) /* INCR8 burst */ +#define DWC3_GSBUSCFG0_INCR4BRSTENA (1 << 1) /* INCR4 burst */ +#define DWC3_GSBUSCFG0_INCRBRSTENA (1 << 0) /* undefined length enable */ +#define DWC3_GSBUSCFG0_INCRBRST_MASK 0xff + +/* Global Debug LSP MUX Select */ +#define DWC3_GDBGLSPMUX_ENDBC BIT(15) /* Host only */ +#define DWC3_GDBGLSPMUX_HOSTSELECT(n) ((n) & 0x3fff) +#define DWC3_GDBGLSPMUX_DEVSELECT(n) (((n) & 0xf) << 4) +#define DWC3_GDBGLSPMUX_EPSELECT(n) ((n) & 0xf) + +/* Global Debug Queue/FIFO Space Available Register */ +#define DWC3_GDBGFIFOSPACE_NUM(n) ((n) & 0x1f) +#define DWC3_GDBGFIFOSPACE_TYPE(n) (((n) << 5) & 0x1e0) +#define DWC3_GDBGFIFOSPACE_SPACE_AVAILABLE(n) (((n) >> 16) & 0xffff) + +#define DWC3_TXFIFO 0 +#define DWC3_RXFIFO 1 +#define DWC3_TXREQQ 2 +#define DWC3_RXREQQ 3 +#define DWC3_RXINFOQ 4 +#define DWC3_PSTATQ 5 +#define DWC3_DESCFETCHQ 6 +#define DWC3_EVENTQ 7 +#define DWC3_AUXEVENTQ 8 + +/* Global RX Threshold Configuration Register */ +#define DWC3_GRXTHRCFG_MAXRXBURSTSIZE(n) (((n) & 0x1f) << 19) +#define DWC3_GRXTHRCFG_RXPKTCNT(n) (((n) & 0xf) << 24) +#define DWC3_GRXTHRCFG_PKTCNTSEL BIT(29) + +/* Global RX Threshold Configuration Register for DWC_usb31 only */ +#define DWC31_GRXTHRCFG_MAXRXBURSTSIZE(n) (((n) & 0x1f) << 16) +#define DWC31_GRXTHRCFG_RXPKTCNT(n) (((n) & 0x1f) << 21) +#define DWC31_GRXTHRCFG_PKTCNTSEL BIT(26) +#define DWC31_RXTHRNUMPKTSEL_HS_PRD BIT(15) +#define DWC31_RXTHRNUMPKT_HS_PRD(n) (((n) & 0x3) << 13) +#define DWC31_RXTHRNUMPKTSEL_PRD BIT(10) +#define DWC31_RXTHRNUMPKT_PRD(n) (((n) & 0x1f) << 5) +#define DWC31_MAXRXBURSTSIZE_PRD(n) ((n) & 0x1f) + +/* Global TX Threshold Configuration Register for DWC_usb31 only */ +#define DWC31_GTXTHRCFG_MAXTXBURSTSIZE(n) (((n) & 0x1f) << 16) +#define DWC31_GTXTHRCFG_TXPKTCNT(n) (((n) & 0x1f) << 21) +#define DWC31_GTXTHRCFG_PKTCNTSEL BIT(26) +#define DWC31_TXTHRNUMPKTSEL_HS_PRD BIT(15) +#define DWC31_TXTHRNUMPKT_HS_PRD(n) (((n) & 0x3) << 13) +#define DWC31_TXTHRNUMPKTSEL_PRD BIT(10) +#define DWC31_TXTHRNUMPKT_PRD(n) (((n) & 0x1f) << 5) +#define DWC31_MAXTXBURSTSIZE_PRD(n) ((n) & 0x1f) + +/* Global Configuration Register */ +#define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19) +#define DWC3_GCTL_U2RSTECN BIT(16) +#define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6) +#define DWC3_GCTL_CLK_BUS (0) +#define DWC3_GCTL_CLK_PIPE (1) +#define DWC3_GCTL_CLK_PIPEHALF (2) +#define DWC3_GCTL_CLK_MASK (3) + +#define DWC3_GCTL_PRTCAP(n) (((n) & (3 << 12)) >> 12) +#define DWC3_GCTL_PRTCAPDIR(n) ((n) << 12) +#define DWC3_GCTL_PRTCAP_HOST 1 +#define DWC3_GCTL_PRTCAP_DEVICE 2 +#define DWC3_GCTL_PRTCAP_OTG 3 + +#define DWC3_GCTL_CORESOFTRESET BIT(11) +#define DWC3_GCTL_SOFITPSYNC BIT(10) +#define DWC3_GCTL_SCALEDOWN(n) ((n) << 4) +#define DWC3_GCTL_SCALEDOWN_MASK DWC3_GCTL_SCALEDOWN(3) +#define DWC3_GCTL_DISSCRAMBLE BIT(3) +#define DWC3_GCTL_U2EXIT_LFPS BIT(2) +#define DWC3_GCTL_GBLHIBERNATIONEN BIT(1) +#define DWC3_GCTL_DSBLCLKGTNG BIT(0) + +/* Global User Control Register */ +#define DWC3_GUCTL_HSTINAUTORETRY BIT(14) + +/* Global User Control 1 Register */ +#define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28) +#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) + +/* Global Status Register */ +#define DWC3_GSTS_OTG_IP BIT(10) +#define DWC3_GSTS_BC_IP BIT(9) +#define DWC3_GSTS_ADP_IP BIT(8) +#define DWC3_GSTS_HOST_IP BIT(7) +#define DWC3_GSTS_DEVICE_IP BIT(6) +#define DWC3_GSTS_CSR_TIMEOUT BIT(5) +#define DWC3_GSTS_BUS_ERR_ADDR_VLD BIT(4) +#define DWC3_GSTS_CURMOD(n) ((n) & 0x3) +#define DWC3_GSTS_CURMOD_DEVICE 0 +#define DWC3_GSTS_CURMOD_HOST 1 + +/* Global USB2 PHY Configuration Register */ +#define DWC3_GUSB2PHYCFG_PHYSOFTRST BIT(31) +#define DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS BIT(30) +#define DWC3_GUSB2PHYCFG_SUSPHY BIT(6) +#define DWC3_GUSB2PHYCFG_ULPI_UTMI BIT(4) +#define DWC3_GUSB2PHYCFG_ENBLSLPM BIT(8) +#define DWC3_GUSB2PHYCFG_PHYIF(n) (n << 3) +#define DWC3_GUSB2PHYCFG_PHYIF_MASK DWC3_GUSB2PHYCFG_PHYIF(1) +#define DWC3_GUSB2PHYCFG_USBTRDTIM(n) (n << 10) +#define DWC3_GUSB2PHYCFG_USBTRDTIM_MASK DWC3_GUSB2PHYCFG_USBTRDTIM(0xf) +#define USBTRDTIM_UTMI_8_BIT 9 +#define USBTRDTIM_UTMI_16_BIT 5 +#define UTMI_PHYIF_16_BIT 1 +#define UTMI_PHYIF_8_BIT 0 + +/* Global USB2 PHY Vendor Control Register */ +#define DWC3_GUSB2PHYACC_NEWREGREQ BIT(25) +#define DWC3_GUSB2PHYACC_BUSY BIT(23) +#define DWC3_GUSB2PHYACC_WRITE BIT(22) +#define DWC3_GUSB2PHYACC_ADDR(n) (n << 16) +#define DWC3_GUSB2PHYACC_EXTEND_ADDR(n) (n << 8) +#define DWC3_GUSB2PHYACC_DATA(n) (n & 0xff) + +/* Global USB3 PIPE Control Register */ +#define DWC3_GUSB3PIPECTL_PHYSOFTRST BIT(31) +#define DWC3_GUSB3PIPECTL_U2SSINP3OK BIT(29) +#define DWC3_GUSB3PIPECTL_DISRXDETINP3 BIT(28) +#define DWC3_GUSB3PIPECTL_UX_EXIT_PX BIT(27) +#define DWC3_GUSB3PIPECTL_REQP1P2P3 BIT(24) +#define DWC3_GUSB3PIPECTL_DEP1P2P3(n) ((n) << 19) +#define DWC3_GUSB3PIPECTL_DEP1P2P3_MASK DWC3_GUSB3PIPECTL_DEP1P2P3(7) +#define DWC3_GUSB3PIPECTL_DEP1P2P3_EN DWC3_GUSB3PIPECTL_DEP1P2P3(1) +#define DWC3_GUSB3PIPECTL_DEPOCHANGE BIT(18) +#define DWC3_GUSB3PIPECTL_SUSPHY BIT(17) +#define DWC3_GUSB3PIPECTL_LFPSFILT BIT(9) +#define DWC3_GUSB3PIPECTL_RX_DETOPOLL BIT(8) +#define DWC3_GUSB3PIPECTL_TX_DEEPH_MASK DWC3_GUSB3PIPECTL_TX_DEEPH(3) +#define DWC3_GUSB3PIPECTL_TX_DEEPH(n) ((n) << 1) + +/* Global TX Fifo Size Register */ +#define DWC31_GTXFIFOSIZ_TXFRAMNUM BIT(15) /* DWC_usb31 only */ +#define DWC31_GTXFIFOSIZ_TXFDEF(n) ((n) & 0x7fff) /* DWC_usb31 only */ +#define DWC3_GTXFIFOSIZ_TXFDEF(n) ((n) & 0xffff) +#define DWC3_GTXFIFOSIZ_TXFSTADDR(n) ((n) & 0xffff0000) + +/* Global Event Size Registers */ +#define DWC3_GEVNTSIZ_INTMASK BIT(31) +#define DWC3_GEVNTSIZ_SIZE(n) ((n) & 0xffff) + +/* Global HWPARAMS0 Register */ +#define DWC3_GHWPARAMS0_MODE(n) ((n) & 0x3) +#define DWC3_GHWPARAMS0_MODE_GADGET 0 +#define DWC3_GHWPARAMS0_MODE_HOST 1 +#define DWC3_GHWPARAMS0_MODE_DRD 2 +#define DWC3_GHWPARAMS0_MBUS_TYPE(n) (((n) >> 3) & 0x7) +#define DWC3_GHWPARAMS0_SBUS_TYPE(n) (((n) >> 6) & 0x3) +#define DWC3_GHWPARAMS0_MDWIDTH(n) (((n) >> 8) & 0xff) +#define DWC3_GHWPARAMS0_SDWIDTH(n) (((n) >> 16) & 0xff) +#define DWC3_GHWPARAMS0_AWIDTH(n) (((n) >> 24) & 0xff) + +/* Global HWPARAMS1 Register */ +#define DWC3_GHWPARAMS1_EN_PWROPT(n) (((n) & (3 << 24)) >> 24) +#define DWC3_GHWPARAMS1_EN_PWROPT_NO 0 +#define DWC3_GHWPARAMS1_EN_PWROPT_CLK 1 +#define DWC3_GHWPARAMS1_EN_PWROPT_HIB 2 +#define DWC3_GHWPARAMS1_PWROPT(n) ((n) << 24) +#define DWC3_GHWPARAMS1_PWROPT_MASK DWC3_GHWPARAMS1_PWROPT(3) +#define DWC3_GHWPARAMS1_ENDBC BIT(31) + +/* Global HWPARAMS3 Register */ +#define DWC3_GHWPARAMS3_SSPHY_IFC(n) ((n) & 3) +#define DWC3_GHWPARAMS3_SSPHY_IFC_DIS 0 +#define DWC3_GHWPARAMS3_SSPHY_IFC_GEN1 1 +#define DWC3_GHWPARAMS3_SSPHY_IFC_GEN2 2 /* DWC_usb31 only */ +#define DWC3_GHWPARAMS3_HSPHY_IFC(n) (((n) & (3 << 2)) >> 2) +#define DWC3_GHWPARAMS3_HSPHY_IFC_DIS 0 +#define DWC3_GHWPARAMS3_HSPHY_IFC_UTMI 1 +#define DWC3_GHWPARAMS3_HSPHY_IFC_ULPI 2 +#define DWC3_GHWPARAMS3_HSPHY_IFC_UTMI_ULPI 3 +#define DWC3_GHWPARAMS3_FSPHY_IFC(n) (((n) & (3 << 4)) >> 4) +#define DWC3_GHWPARAMS3_FSPHY_IFC_DIS 0 +#define DWC3_GHWPARAMS3_FSPHY_IFC_ENA 1 + +/* Global HWPARAMS4 Register */ +#define DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(n) (((n) & (0x0f << 13)) >> 13) +#define DWC3_MAX_HIBER_SCRATCHBUFS 15 + +/* Global HWPARAMS6 Register */ +#define DWC3_GHWPARAMS6_BCSUPPORT BIT(14) +#define DWC3_GHWPARAMS6_OTG3SUPPORT BIT(13) +#define DWC3_GHWPARAMS6_ADPSUPPORT BIT(12) +#define DWC3_GHWPARAMS6_HNPSUPPORT BIT(11) +#define DWC3_GHWPARAMS6_SRPSUPPORT BIT(10) +#define DWC3_GHWPARAMS6_EN_FPGA BIT(7) + +/* Global HWPARAMS7 Register */ +#define DWC3_GHWPARAMS7_RAM1_DEPTH(n) ((n) & 0xffff) +#define DWC3_GHWPARAMS7_RAM2_DEPTH(n) (((n) >> 16) & 0xffff) + +/* Global Frame Length Adjustment Register */ +#define DWC3_GFLADJ_30MHZ_SDBND_SEL BIT(7) +#define DWC3_GFLADJ_30MHZ_MASK 0x3f + +/* Global User Control Register 2 */ +#define DWC3_GUCTL2_RST_ACTBITLATER BIT(14) + +/* Device Configuration Register */ +#define DWC3_DCFG_DEVADDR(addr) ((addr) << 3) +#define DWC3_DCFG_DEVADDR_MASK DWC3_DCFG_DEVADDR(0x7f) + +#define DWC3_DCFG_SPEED_MASK (7 << 0) +#define DWC3_DCFG_SUPERSPEED_PLUS (5 << 0) /* DWC_usb31 only */ +#define DWC3_DCFG_SUPERSPEED (4 << 0) +#define DWC3_DCFG_HIGHSPEED (0 << 0) +#define DWC3_DCFG_FULLSPEED BIT(0) +#define DWC3_DCFG_LOWSPEED (2 << 0) + +#define DWC3_DCFG_NUMP_SHIFT 17 +#define DWC3_DCFG_NUMP(n) (((n) >> DWC3_DCFG_NUMP_SHIFT) & 0x1f) +#define DWC3_DCFG_NUMP_MASK (0x1f << DWC3_DCFG_NUMP_SHIFT) +#define DWC3_DCFG_LPM_CAP BIT(22) + +/* Device Control Register */ +#define DWC3_DCTL_RUN_STOP BIT(31) +#define DWC3_DCTL_CSFTRST BIT(30) +#define DWC3_DCTL_LSFTRST BIT(29) + +#define DWC3_DCTL_HIRD_THRES_MASK (0x1f << 24) +#define DWC3_DCTL_HIRD_THRES(n) ((n) << 24) + +#define DWC3_DCTL_APPL1RES BIT(23) + +/* These apply for core versions 1.87a and earlier */ +#define DWC3_DCTL_TRGTULST_MASK (0x0f << 17) +#define DWC3_DCTL_TRGTULST(n) ((n) << 17) +#define DWC3_DCTL_TRGTULST_U2 (DWC3_DCTL_TRGTULST(2)) +#define DWC3_DCTL_TRGTULST_U3 (DWC3_DCTL_TRGTULST(3)) +#define DWC3_DCTL_TRGTULST_SS_DIS (DWC3_DCTL_TRGTULST(4)) +#define DWC3_DCTL_TRGTULST_RX_DET (DWC3_DCTL_TRGTULST(5)) +#define DWC3_DCTL_TRGTULST_SS_INACT (DWC3_DCTL_TRGTULST(6)) + +/* These apply for core versions 1.94a and later */ +#define DWC3_DCTL_LPM_ERRATA_MASK DWC3_DCTL_LPM_ERRATA(0xf) +#define DWC3_DCTL_LPM_ERRATA(n) ((n) << 20) + +#define DWC3_DCTL_KEEP_CONNECT BIT(19) +#define DWC3_DCTL_L1_HIBER_EN BIT(18) +#define DWC3_DCTL_CRS BIT(17) +#define DWC3_DCTL_CSS BIT(16) + +#define DWC3_DCTL_INITU2ENA BIT(12) +#define DWC3_DCTL_ACCEPTU2ENA BIT(11) +#define DWC3_DCTL_INITU1ENA BIT(10) +#define DWC3_DCTL_ACCEPTU1ENA BIT(9) +#define DWC3_DCTL_TSTCTRL_MASK (0xf << 1) + +#define DWC3_DCTL_ULSTCHNGREQ_MASK (0x0f << 5) +#define DWC3_DCTL_ULSTCHNGREQ(n) (((n) << 5) & DWC3_DCTL_ULSTCHNGREQ_MASK) + +#define DWC3_DCTL_ULSTCHNG_NO_ACTION (DWC3_DCTL_ULSTCHNGREQ(0)) +#define DWC3_DCTL_ULSTCHNG_SS_DISABLED (DWC3_DCTL_ULSTCHNGREQ(4)) +#define DWC3_DCTL_ULSTCHNG_RX_DETECT (DWC3_DCTL_ULSTCHNGREQ(5)) +#define DWC3_DCTL_ULSTCHNG_SS_INACTIVE (DWC3_DCTL_ULSTCHNGREQ(6)) +#define DWC3_DCTL_ULSTCHNG_RECOVERY (DWC3_DCTL_ULSTCHNGREQ(8)) +#define DWC3_DCTL_ULSTCHNG_COMPLIANCE (DWC3_DCTL_ULSTCHNGREQ(10)) +#define DWC3_DCTL_ULSTCHNG_LOOPBACK (DWC3_DCTL_ULSTCHNGREQ(11)) + +/* Device Event Enable Register */ +#define DWC3_DEVTEN_VNDRDEVTSTRCVEDEN BIT(12) +#define DWC3_DEVTEN_EVNTOVERFLOWEN BIT(11) +#define DWC3_DEVTEN_CMDCMPLTEN BIT(10) +#define DWC3_DEVTEN_ERRTICERREN BIT(9) +#define DWC3_DEVTEN_SOFEN BIT(7) +#define DWC3_DEVTEN_EOPFEN BIT(6) +#define DWC3_DEVTEN_HIBERNATIONREQEVTEN BIT(5) +#define DWC3_DEVTEN_WKUPEVTEN BIT(4) +#define DWC3_DEVTEN_ULSTCNGEN BIT(3) +#define DWC3_DEVTEN_CONNECTDONEEN BIT(2) +#define DWC3_DEVTEN_USBRSTEN BIT(1) +#define DWC3_DEVTEN_DISCONNEVTEN BIT(0) + +/* Device Status Register */ +#define DWC3_DSTS_DCNRD BIT(29) + +/* This applies for core versions 1.87a and earlier */ +#define DWC3_DSTS_PWRUPREQ BIT(24) + +/* These apply for core versions 1.94a and later */ +#define DWC3_DSTS_RSS BIT(25) +#define DWC3_DSTS_SSS BIT(24) + +#define DWC3_DSTS_COREIDLE BIT(23) +#define DWC3_DSTS_DEVCTRLHLT BIT(22) + +#define DWC3_DSTS_USBLNKST_MASK (0x0f << 18) +#define DWC3_DSTS_USBLNKST(n) (((n) & DWC3_DSTS_USBLNKST_MASK) >> 18) + +#define DWC3_DSTS_RXFIFOEMPTY BIT(17) + +#define DWC3_DSTS_SOFFN_MASK (0x3fff << 3) +#define DWC3_DSTS_SOFFN(n) (((n) & DWC3_DSTS_SOFFN_MASK) >> 3) + +#define DWC3_DSTS_CONNECTSPD (7 << 0) + +#define DWC3_DSTS_SUPERSPEED_PLUS (5 << 0) /* DWC_usb31 only */ +#define DWC3_DSTS_SUPERSPEED (4 << 0) +#define DWC3_DSTS_HIGHSPEED (0 << 0) +#define DWC3_DSTS_FULLSPEED BIT(0) +#define DWC3_DSTS_LOWSPEED (2 << 0) + +/* Device Generic Command Register */ +#define DWC3_DGCMD_SET_LMP 0x01 +#define DWC3_DGCMD_SET_PERIODIC_PAR 0x02 +#define DWC3_DGCMD_XMIT_FUNCTION 0x03 + +/* These apply for core versions 1.94a and later */ +#define DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO 0x04 +#define DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI 0x05 + +#define DWC3_DGCMD_SELECTED_FIFO_FLUSH 0x09 +#define DWC3_DGCMD_ALL_FIFO_FLUSH 0x0a +#define DWC3_DGCMD_SET_ENDPOINT_NRDY 0x0c +#define DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK 0x10 + +#define DWC3_DGCMD_STATUS(n) (((n) >> 12) & 0x0F) +#define DWC3_DGCMD_CMDACT BIT(10) +#define DWC3_DGCMD_CMDIOC BIT(8) + +/* Device Generic Command Parameter Register */ +#define DWC3_DGCMDPAR_FORCE_LINKPM_ACCEPT BIT(0) +#define DWC3_DGCMDPAR_FIFO_NUM(n) ((n) << 0) +#define DWC3_DGCMDPAR_RX_FIFO (0 << 5) +#define DWC3_DGCMDPAR_TX_FIFO BIT(5) +#define DWC3_DGCMDPAR_LOOPBACK_DIS (0 << 0) +#define DWC3_DGCMDPAR_LOOPBACK_ENA BIT(0) + +/* Device Endpoint Command Register */ +#define DWC3_DEPCMD_PARAM_SHIFT 16 +#define DWC3_DEPCMD_PARAM(x) ((x) << DWC3_DEPCMD_PARAM_SHIFT) +#define DWC3_DEPCMD_GET_RSC_IDX(x) (((x) >> DWC3_DEPCMD_PARAM_SHIFT) & 0x7f) +#define DWC3_DEPCMD_STATUS(x) (((x) >> 12) & 0x0F) +#define DWC3_DEPCMD_HIPRI_FORCERM BIT(11) +#define DWC3_DEPCMD_CLEARPENDIN BIT(11) +#define DWC3_DEPCMD_CMDACT BIT(10) +#define DWC3_DEPCMD_CMDIOC BIT(8) + +#define DWC3_DEPCMD_DEPSTARTCFG (0x09 << 0) +#define DWC3_DEPCMD_ENDTRANSFER (0x08 << 0) +#define DWC3_DEPCMD_UPDATETRANSFER (0x07 << 0) +#define DWC3_DEPCMD_STARTTRANSFER (0x06 << 0) +#define DWC3_DEPCMD_CLEARSTALL (0x05 << 0) +#define DWC3_DEPCMD_SETSTALL (0x04 << 0) +/* This applies for core versions 1.90a and earlier */ +#define DWC3_DEPCMD_GETSEQNUMBER (0x03 << 0) +/* This applies for core versions 1.94a and later */ +#define DWC3_DEPCMD_GETEPSTATE (0x03 << 0) +#define DWC3_DEPCMD_SETTRANSFRESOURCE (0x02 << 0) +#define DWC3_DEPCMD_SETEPCONFIG (0x01 << 0) + +#define DWC3_DEPCMD_CMD(x) ((x) & 0xf) + +/* The EP number goes 0..31 so ep0 is always out and ep1 is always in */ +#define DWC3_DALEPENA_EP(n) BIT(n) + +#define DWC3_DEPCMD_TYPE_CONTROL 0 +#define DWC3_DEPCMD_TYPE_ISOC 1 +#define DWC3_DEPCMD_TYPE_BULK 2 +#define DWC3_DEPCMD_TYPE_INTR 3 + +#define DWC3_DEV_IMOD_COUNT_SHIFT 16 +#define DWC3_DEV_IMOD_COUNT_MASK (0xffff << 16) +#define DWC3_DEV_IMOD_INTERVAL_SHIFT 0 +#define DWC3_DEV_IMOD_INTERVAL_MASK (0xffff << 0) + +/* OTG Configuration Register */ +#define DWC3_OCFG_DISPWRCUTTOFF BIT(5) +#define DWC3_OCFG_HIBDISMASK BIT(4) +#define DWC3_OCFG_SFTRSTMASK BIT(3) +#define DWC3_OCFG_OTGVERSION BIT(2) +#define DWC3_OCFG_HNPCAP BIT(1) +#define DWC3_OCFG_SRPCAP BIT(0) + +/* OTG CTL Register */ +#define DWC3_OCTL_OTG3GOERR BIT(7) +#define DWC3_OCTL_PERIMODE BIT(6) +#define DWC3_OCTL_PRTPWRCTL BIT(5) +#define DWC3_OCTL_HNPREQ BIT(4) +#define DWC3_OCTL_SESREQ BIT(3) +#define DWC3_OCTL_TERMSELIDPULSE BIT(2) +#define DWC3_OCTL_DEVSETHNPEN BIT(1) +#define DWC3_OCTL_HSTSETHNPEN BIT(0) + +/* OTG Event Register */ +#define DWC3_OEVT_DEVICEMODE BIT(31) +#define DWC3_OEVT_XHCIRUNSTPSET BIT(27) +#define DWC3_OEVT_DEVRUNSTPSET BIT(26) +#define DWC3_OEVT_HIBENTRY BIT(25) +#define DWC3_OEVT_CONIDSTSCHNG BIT(24) +#define DWC3_OEVT_HRRCONFNOTIF BIT(23) +#define DWC3_OEVT_HRRINITNOTIF BIT(22) +#define DWC3_OEVT_ADEVIDLE BIT(21) +#define DWC3_OEVT_ADEVBHOSTEND BIT(20) +#define DWC3_OEVT_ADEVHOST BIT(19) +#define DWC3_OEVT_ADEVHNPCHNG BIT(18) +#define DWC3_OEVT_ADEVSRPDET BIT(17) +#define DWC3_OEVT_ADEVSESSENDDET BIT(16) +#define DWC3_OEVT_BDEVBHOSTEND BIT(11) +#define DWC3_OEVT_BDEVHNPCHNG BIT(10) +#define DWC3_OEVT_BDEVSESSVLDDET BIT(9) +#define DWC3_OEVT_BDEVVBUSCHNG BIT(8) +#define DWC3_OEVT_BSESSVLD BIT(3) +#define DWC3_OEVT_HSTNEGSTS BIT(2) +#define DWC3_OEVT_SESREQSTS BIT(1) +#define DWC3_OEVT_ERROR BIT(0) + +/* OTG Event Enable Register */ +#define DWC3_OEVTEN_XHCIRUNSTPSETEN BIT(27) +#define DWC3_OEVTEN_DEVRUNSTPSETEN BIT(26) +#define DWC3_OEVTEN_HIBENTRYEN BIT(25) +#define DWC3_OEVTEN_CONIDSTSCHNGEN BIT(24) +#define DWC3_OEVTEN_HRRCONFNOTIFEN BIT(23) +#define DWC3_OEVTEN_HRRINITNOTIFEN BIT(22) +#define DWC3_OEVTEN_ADEVIDLEEN BIT(21) +#define DWC3_OEVTEN_ADEVBHOSTENDEN BIT(20) +#define DWC3_OEVTEN_ADEVHOSTEN BIT(19) +#define DWC3_OEVTEN_ADEVHNPCHNGEN BIT(18) +#define DWC3_OEVTEN_ADEVSRPDETEN BIT(17) +#define DWC3_OEVTEN_ADEVSESSENDDETEN BIT(16) +#define DWC3_OEVTEN_BDEVBHOSTENDEN BIT(11) +#define DWC3_OEVTEN_BDEVHNPCHNGEN BIT(10) +#define DWC3_OEVTEN_BDEVSESSVLDDETEN BIT(9) +#define DWC3_OEVTEN_BDEVVBUSCHNGEN BIT(8) + +/* OTG Status Register */ +#define DWC3_OSTS_DEVRUNSTP BIT(13) +#define DWC3_OSTS_XHCIRUNSTP BIT(12) +#define DWC3_OSTS_PERIPHERALSTATE BIT(4) +#define DWC3_OSTS_XHCIPRTPOWER BIT(3) +#define DWC3_OSTS_BSESVLD BIT(2) +#define DWC3_OSTS_VBUSVLD BIT(1) +#define DWC3_OSTS_CONIDSTS BIT(0) + +/* Structures */ + +struct dwc3_trb; + +#define DWC3_EP_FLAG_STALLED BIT(0) +#define DWC3_EP_FLAG_WEDGED BIT(1) + +#define DWC3_EP_DIRECTION_TX true +#define DWC3_EP_DIRECTION_RX false + +#define DWC3_TRB_NUM 256 + +enum dwc3_phy { + DWC3_PHY_UNKNOWN = 0, + DWC3_PHY_USB3, + DWC3_PHY_USB2, +}; + +enum dwc3_ep0_next { + DWC3_EP0_UNKNOWN = 0, + DWC3_EP0_COMPLETE, + DWC3_EP0_NRDY_DATA, + DWC3_EP0_NRDY_STATUS, +}; + +enum dwc3_ep0_state { + EP0_UNCONNECTED = 0, + EP0_SETUP_PHASE, + EP0_DATA_PHASE, + EP0_STATUS_PHASE, +}; + +enum dwc3_link_state { + /* In SuperSpeed */ + DWC3_LINK_STATE_U0 = 0x00, /* in HS, means ON */ + DWC3_LINK_STATE_U1 = 0x01, + DWC3_LINK_STATE_U2 = 0x02, /* in HS, means SLEEP */ + DWC3_LINK_STATE_U3 = 0x03, /* in HS, means SUSPEND */ + DWC3_LINK_STATE_SS_DIS = 0x04, + DWC3_LINK_STATE_RX_DET = 0x05, /* in HS, means Early Suspend */ + DWC3_LINK_STATE_SS_INACT = 0x06, + DWC3_LINK_STATE_POLL = 0x07, + DWC3_LINK_STATE_RECOV = 0x08, + DWC3_LINK_STATE_HRESET = 0x09, + DWC3_LINK_STATE_CMPLY = 0x0a, + DWC3_LINK_STATE_LPBK = 0x0b, + DWC3_LINK_STATE_RESET = 0x0e, + DWC3_LINK_STATE_RESUME = 0x0f, + DWC3_LINK_STATE_MASK = 0x0f, +}; + +/* TRB Length, PCM and Status */ +#define DWC3_TRB_SIZE_MASK (0x00ffffff) +#define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) +#define DWC3_TRB_SIZE_PCM1(n) (((n) & 0x03) << 24) +#define DWC3_TRB_SIZE_TRBSTS(n) (((n) & (0x0f << 28)) >> 28) + +#define DWC3_TRBSTS_OK 0 +#define DWC3_TRBSTS_MISSED_ISOC 1 +#define DWC3_TRBSTS_SETUP_PENDING 2 +#define DWC3_TRB_STS_XFER_IN_PROG 4 + +/* TRB Control */ +#define DWC3_TRB_CTRL_HWO BIT(0) +#define DWC3_TRB_CTRL_LST BIT(1) +#define DWC3_TRB_CTRL_CHN BIT(2) +#define DWC3_TRB_CTRL_CSP BIT(3) +#define DWC3_TRB_CTRL_TRBCTL(n) (((n) & 0x3f) << 4) +#define DWC3_TRB_CTRL_ISP_IMI BIT(10) +#define DWC3_TRB_CTRL_IOC BIT(11) +#define DWC3_TRB_CTRL_SID_SOFN(n) (((n) & 0xffff) << 14) +#define DWC3_TRB_CTRL_GET_SID_SOFN(n) (((n) & (0xffff << 14)) >> 14) + +#define DWC3_TRBCTL_TYPE(n) ((n) & (0x3f << 4)) +#define DWC3_TRBCTL_NORMAL DWC3_TRB_CTRL_TRBCTL(1) +#define DWC3_TRBCTL_CONTROL_SETUP DWC3_TRB_CTRL_TRBCTL(2) +#define DWC3_TRBCTL_CONTROL_STATUS2 DWC3_TRB_CTRL_TRBCTL(3) +#define DWC3_TRBCTL_CONTROL_STATUS3 DWC3_TRB_CTRL_TRBCTL(4) +#define DWC3_TRBCTL_CONTROL_DATA DWC3_TRB_CTRL_TRBCTL(5) +#define DWC3_TRBCTL_ISOCHRONOUS_FIRST DWC3_TRB_CTRL_TRBCTL(6) +#define DWC3_TRBCTL_ISOCHRONOUS DWC3_TRB_CTRL_TRBCTL(7) +#define DWC3_TRBCTL_LINK_TRB DWC3_TRB_CTRL_TRBCTL(8) + +/** + * struct dwc3_trb - transfer request block (hw format) + * @bpl: DW0-3 + * @bph: DW4-7 + * @size: DW8-B + * @ctrl: DWC-F + */ +struct dwc3_trb { + u32 bpl; + u32 bph; + u32 size; + u32 ctrl; +} __packed; + +/** + * struct dwc3_hwparams - copy of HWPARAMS registers + * @hwparams0: GHWPARAMS0 + * @hwparams1: GHWPARAMS1 + * @hwparams2: GHWPARAMS2 + * @hwparams3: GHWPARAMS3 + * @hwparams4: GHWPARAMS4 + * @hwparams5: GHWPARAMS5 + * @hwparams6: GHWPARAMS6 + * @hwparams7: GHWPARAMS7 + * @hwparams8: GHWPARAMS8 + */ +struct dwc3_hwparams { + u32 hwparams0; + u32 hwparams1; + u32 hwparams2; + u32 hwparams3; + u32 hwparams4; + u32 hwparams5; + u32 hwparams6; + u32 hwparams7; + u32 hwparams8; +}; + +/* HWPARAMS0 */ +#define DWC3_MODE(n) ((n) & 0x7) + +#define DWC3_MDWIDTH(n) (((n) & 0xff00) >> 8) + +/* HWPARAMS1 */ +#define DWC3_NUM_INT(n) (((n) & (0x3f << 15)) >> 15) + +/* HWPARAMS3 */ +#define DWC3_NUM_IN_EPS_MASK (0x1f << 18) +#define DWC3_NUM_EPS_MASK (0x3f << 12) +#define DWC3_NUM_EPS(p) (((p)->hwparams3 & \ + (DWC3_NUM_EPS_MASK)) >> 12) +#define DWC3_NUM_IN_EPS(p) (((p)->hwparams3 & \ + (DWC3_NUM_IN_EPS_MASK)) >> 18) + +/* HWPARAMS7 */ +#define DWC3_RAM1_DEPTH(n) ((n) & 0xffff) + +/** + * struct dwc3 - representation of our controller + * @drd_work: workqueue used for role swapping + * @ep0_trb: trb which is used for the ctrl_req + * @bounce: address of bounce buffer + * @scratchbuf: address of scratch buffer + * @setup_buf: used while precessing STD USB requests + * @ep0_trb_addr: dma address of @ep0_trb + * @bounce_addr: dma address of @bounce + * @ep0_usb_req: dummy req used while handling STD USB requests + * @scratch_addr: dma address of scratchbuf + * @ep0_in_setup: one control transfer is completed and enter setup phase + * @lock: for synchronizing + * @dev: pointer to our struct device + * @sysdev: pointer to the DMA-capable device + * @xhci: pointer to our xHCI child + * @xhci_resources: struct resources for our @xhci child + * @ev_buf: struct dwc3_event_buffer pointer + * @eps: endpoint array + * @gadget: device side representation of the peripheral controller + * @gadget_driver: pointer to the gadget driver + * @clks: array of clocks + * @num_clks: number of clocks + * @reset: reset control + * @regs: base address for our registers + * @regs_size: address space size + * @fladj: frame length adjustment + * @irq_gadget: peripheral controller's IRQ number + * @otg_irq: IRQ number for OTG IRQs + * @current_otg_role: current role of operation while using the OTG block + * @desired_otg_role: desired role of operation while using the OTG block + * @otg_restart_host: flag that OTG controller needs to restart host + * @nr_scratch: number of scratch buffers + * @u1u2: only used on revisions <1.83a for workaround + * @maximum_speed: maximum speed requested (mainly for testing purposes) + * @revision: revision register contents + * @version_type: VERSIONTYPE register contents, a sub release of a revision + * @dr_mode: requested mode of operation + * @current_dr_role: current role of operation when in dual-role mode + * @desired_dr_role: desired role of operation when in dual-role mode + * @edev: extcon handle + * @edev_nb: extcon notifier + * @hsphy_mode: UTMI phy mode, one of following: + * - USBPHY_INTERFACE_MODE_UTMI + * - USBPHY_INTERFACE_MODE_UTMIW + * @usb2_phy: pointer to USB2 PHY + * @usb3_phy: pointer to USB3 PHY + * @usb2_generic_phy: pointer to USB2 PHY + * @usb3_generic_phy: pointer to USB3 PHY + * @phys_ready: flag to indicate that PHYs are ready + * @ulpi: pointer to ulpi interface + * @ulpi_ready: flag to indicate that ULPI is initialized + * @u2sel: parameter from Set SEL request. + * @u2pel: parameter from Set SEL request. + * @u1sel: parameter from Set SEL request. + * @u1pel: parameter from Set SEL request. + * @num_eps: number of endpoints + * @ep0_next_event: hold the next expected event + * @ep0state: state of endpoint zero + * @link_state: link state + * @speed: device speed (super, high, full, low) + * @hwparams: copy of hwparams registers + * @root: debugfs root folder pointer + * @regset: debugfs pointer to regdump file + * @dbg_lsp_select: current debug lsp mux register selection + * @test_mode: true when we're entering a USB test mode + * @test_mode_nr: test feature selector + * @lpm_nyet_threshold: LPM NYET response threshold + * @hird_threshold: HIRD threshold + * @rx_thr_num_pkt_prd: periodic ESS receive packet count + * @rx_max_burst_prd: max periodic ESS receive burst size + * @tx_thr_num_pkt_prd: periodic ESS transmit packet count + * @tx_max_burst_prd: max periodic ESS transmit burst size + * @hsphy_interface: "utmi" or "ulpi" + * @connected: true when we're connected to a host, false otherwise + * @delayed_status: true when gadget driver asks for delayed status + * @ep0_bounced: true when we used bounce buffer + * @ep0_expect_in: true when we expect a DATA IN transfer + * @has_hibernation: true when dwc3 was configured with Hibernation + * @sysdev_is_parent: true when dwc3 device has a parent driver + * @has_lpm_erratum: true when core was configured with LPM Erratum. Note that + * there's now way for software to detect this in runtime. + * @is_utmi_l1_suspend: the core asserts output signal + * 0 - utmi_sleep_n + * 1 - utmi_l1_suspend_n + * @is_fpga: true when we are using the FPGA board + * @pending_events: true when we have pending IRQs to be handled + * @pullups_connected: true when Run/Stop bit is set + * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround + * @three_stage_setup: set if we perform a three phase setup + * @dis_start_transfer_quirk: set if start_transfer failure SW workaround is + * not needed for DWC_usb31 version 1.70a-ea06 and below + * @usb3_lpm_capable: set if hadrware supports Link Power Management + * @usb2_lpm_disable: set to disable usb2 lpm + * @disable_scramble_quirk: set if we enable the disable scramble quirk + * @u2exit_lfps_quirk: set if we enable u2exit lfps quirk + * @u2ss_inp3_quirk: set if we enable P3 OK for U2/SS Inactive quirk + * @req_p1p2p3_quirk: set if we enable request p1p2p3 quirk + * @del_p1p2p3_quirk: set if we enable delay p1p2p3 quirk + * @del_phy_power_chg_quirk: set if we enable delay phy power change quirk + * @lfps_filter_quirk: set if we enable LFPS filter quirk + * @rx_detect_poll_quirk: set if we enable rx_detect to polling lfps quirk + * @dis_u3_susphy_quirk: set if we disable usb3 suspend phy + * @dis_u2_susphy_quirk: set if we disable usb2 suspend phy + * @dis_enblslpm_quirk: set if we clear enblslpm in GUSB2PHYCFG, + * disabling the suspend signal to the PHY. + * @dis_rxdet_inp3_quirk: set if we disable Rx.Detect in P3 + * @dis_u2_freeclk_exists_quirk : set if we clear u2_freeclk_exists + * in GUSB2PHYCFG, specify that USB2 PHY doesn't + * provide a free-running PHY clock. + * @dis_del_phy_power_chg_quirk: set if we disable delay phy power + * change quirk. + * @dis_tx_ipgap_linecheck_quirk: set if we disable u2mac linestate + * check during HS transmit. + * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk + * @tx_de_emphasis: Tx de-emphasis value + * 0 - -6dB de-emphasis + * 1 - -3.5dB de-emphasis + * 2 - No de-emphasis + * 3 - Reserved + * @dis_metastability_quirk: set to disable metastability quirk. + * @imod_interval: set the interrupt moderation interval in 250ns + * increments or 0 to disable. + */ +struct dwc3 { + struct device_d *dev; + struct device_d *xhci; + + struct clk_bulk_data *clks; + int num_clks; + + struct phy *usb2_generic_phy; + struct phy *usb3_generic_phy; + + bool phys_ready; + + void __iomem *regs; + + enum usb_dr_mode dr_mode; + u32 current_dr_role; + u32 desired_dr_role; + + u32 fladj; + u32 irq_gadget; + u32 otg_irq; + u32 current_otg_role; + u32 desired_otg_role; + bool otg_restart_host; + u32 nr_scratch; + u32 u1u2; + u32 maximum_speed; + + /* + * All 3.1 IP version constants are greater than the 3.0 IP + * version constants. This works for most version checks in + * dwc3. However, in the future, this may not apply as + * features may be developed on newer versions of the 3.0 IP + * that are not in the 3.1 IP. + */ + u32 revision; + +#define DWC3_REVISION_173A 0x5533173a +#define DWC3_REVISION_175A 0x5533175a +#define DWC3_REVISION_180A 0x5533180a +#define DWC3_REVISION_183A 0x5533183a +#define DWC3_REVISION_185A 0x5533185a +#define DWC3_REVISION_187A 0x5533187a +#define DWC3_REVISION_188A 0x5533188a +#define DWC3_REVISION_190A 0x5533190a +#define DWC3_REVISION_194A 0x5533194a +#define DWC3_REVISION_200A 0x5533200a +#define DWC3_REVISION_202A 0x5533202a +#define DWC3_REVISION_210A 0x5533210a +#define DWC3_REVISION_220A 0x5533220a +#define DWC3_REVISION_230A 0x5533230a +#define DWC3_REVISION_240A 0x5533240a +#define DWC3_REVISION_250A 0x5533250a +#define DWC3_REVISION_260A 0x5533260a +#define DWC3_REVISION_270A 0x5533270a +#define DWC3_REVISION_280A 0x5533280a +#define DWC3_REVISION_290A 0x5533290a +#define DWC3_REVISION_300A 0x5533300a +#define DWC3_REVISION_310A 0x5533310a +#define DWC3_REVISION_330A 0x5533330a + +/* + * NOTICE: we're using bit 31 as a "is usb 3.1" flag. This is really + * just so dwc31 revisions are always larger than dwc3. + */ +#define DWC3_REVISION_IS_DWC31 0x80000000 +#define DWC3_USB31_REVISION_110A (0x3131302a | DWC3_REVISION_IS_DWC31) +#define DWC3_USB31_REVISION_120A (0x3132302a | DWC3_REVISION_IS_DWC31) +#define DWC3_USB31_REVISION_160A (0x3136302a | DWC3_REVISION_IS_DWC31) +#define DWC3_USB31_REVISION_170A (0x3137302a | DWC3_REVISION_IS_DWC31) + + u32 version_type; + +#define DWC31_VERSIONTYPE_EA01 0x65613031 +#define DWC31_VERSIONTYPE_EA02 0x65613032 +#define DWC31_VERSIONTYPE_EA03 0x65613033 +#define DWC31_VERSIONTYPE_EA04 0x65613034 +#define DWC31_VERSIONTYPE_EA05 0x65613035 +#define DWC31_VERSIONTYPE_EA06 0x65613036 + + u16 u2sel; + u16 u2pel; + u8 u1sel; + u8 u1pel; + + u8 speed; + + u8 num_eps; + + struct dwc3_hwparams hwparams; + + u32 dbg_lsp_select; + + u8 test_mode; + u8 test_mode_nr; + u8 lpm_nyet_threshold; + u8 hird_threshold; + u8 rx_thr_num_pkt_prd; + u8 rx_max_burst_prd; + u8 tx_thr_num_pkt_prd; + u8 tx_max_burst_prd; + + const char *hsphy_interface; + + unsigned connected:1; + unsigned delayed_status:1; + unsigned ep0_bounced:1; + unsigned ep0_expect_in:1; + unsigned has_hibernation:1; + unsigned sysdev_is_parent:1; + unsigned has_lpm_erratum:1; + unsigned is_utmi_l1_suspend:1; + unsigned is_fpga:1; + unsigned pending_events:1; + unsigned pullups_connected:1; + unsigned setup_packet_pending:1; + unsigned three_stage_setup:1; + unsigned dis_start_transfer_quirk:1; + unsigned usb3_lpm_capable:1; + unsigned usb2_lpm_disable:1; + + unsigned disable_scramble_quirk:1; + unsigned u2exit_lfps_quirk:1; + unsigned u2ss_inp3_quirk:1; + unsigned req_p1p2p3_quirk:1; + unsigned del_p1p2p3_quirk:1; + unsigned del_phy_power_chg_quirk:1; + unsigned lfps_filter_quirk:1; + unsigned rx_detect_poll_quirk:1; + unsigned dis_u3_susphy_quirk:1; + unsigned dis_u2_susphy_quirk:1; + unsigned dis_enblslpm_quirk:1; + unsigned dis_rxdet_inp3_quirk:1; + unsigned dis_u2_freeclk_exists_quirk:1; + unsigned dis_del_phy_power_chg_quirk:1; + unsigned dis_tx_ipgap_linecheck_quirk:1; + + unsigned tx_de_emphasis_quirk:1; + unsigned tx_de_emphasis:2; + + unsigned dis_metastability_quirk:1; + + u16 imod_interval; +}; + +#define INCRX_BURST_MODE 0 +#define INCRX_UNDEF_LENGTH_BURST_MODE 1 + +#define work_to_dwc(w) (container_of((w), struct dwc3, drd_work)) + +/* -------------------------------------------------------------------------- */ + +struct dwc3_event_type { + u32 is_devspec:1; + u32 type:7; + u32 reserved8_31:24; +} __packed; + +#define DWC3_DEPEVT_XFERCOMPLETE 0x01 +#define DWC3_DEPEVT_XFERINPROGRESS 0x02 +#define DWC3_DEPEVT_XFERNOTREADY 0x03 +#define DWC3_DEPEVT_RXTXFIFOEVT 0x04 +#define DWC3_DEPEVT_STREAMEVT 0x06 +#define DWC3_DEPEVT_EPCMDCMPLT 0x07 + +/** + * struct dwc3_event_depvt - Device Endpoint Events + * @one_bit: indicates this is an endpoint event (not used) + * @endpoint_number: number of the endpoint + * @endpoint_event: The event we have: + * 0x00 - Reserved + * 0x01 - XferComplete + * 0x02 - XferInProgress + * 0x03 - XferNotReady + * 0x04 - RxTxFifoEvt (IN->Underrun, OUT->Overrun) + * 0x05 - Reserved + * 0x06 - StreamEvt + * 0x07 - EPCmdCmplt + * @reserved11_10: Reserved, don't use. + * @status: Indicates the status of the event. Refer to databook for + * more information. + * @parameters: Parameters of the current event. Refer to databook for + * more information. + */ +struct dwc3_event_depevt { + u32 one_bit:1; + u32 endpoint_number:5; + u32 endpoint_event:4; + u32 reserved11_10:2; + u32 status:4; + +/* Within XferNotReady */ +#define DEPEVT_STATUS_TRANSFER_ACTIVE BIT(3) + +/* Within XferComplete or XferInProgress */ +#define DEPEVT_STATUS_BUSERR BIT(0) +#define DEPEVT_STATUS_SHORT BIT(1) +#define DEPEVT_STATUS_IOC BIT(2) +#define DEPEVT_STATUS_LST BIT(3) /* XferComplete */ +#define DEPEVT_STATUS_MISSED_ISOC BIT(3) /* XferInProgress */ + +/* Stream event only */ +#define DEPEVT_STREAMEVT_FOUND 1 +#define DEPEVT_STREAMEVT_NOTFOUND 2 + +/* Control-only Status */ +#define DEPEVT_STATUS_CONTROL_DATA 1 +#define DEPEVT_STATUS_CONTROL_STATUS 2 +#define DEPEVT_STATUS_CONTROL_PHASE(n) ((n) & 3) + +/* In response to Start Transfer */ +#define DEPEVT_TRANSFER_NO_RESOURCE 1 +#define DEPEVT_TRANSFER_BUS_EXPIRY 2 + + u32 parameters:16; + +/* For Command Complete Events */ +#define DEPEVT_PARAMETER_CMD(n) (((n) & (0xf << 8)) >> 8) +} __packed; + +/** + * struct dwc3_event_devt - Device Events + * @one_bit: indicates this is a non-endpoint event (not used) + * @device_event: indicates it's a device event. Should read as 0x00 + * @type: indicates the type of device event. + * 0 - DisconnEvt + * 1 - USBRst + * 2 - ConnectDone + * 3 - ULStChng + * 4 - WkUpEvt + * 5 - Reserved + * 6 - EOPF + * 7 - SOF + * 8 - Reserved + * 9 - ErrticErr + * 10 - CmdCmplt + * 11 - EvntOverflow + * 12 - VndrDevTstRcved + * @reserved15_12: Reserved, not used + * @event_info: Information about this event + * @reserved31_25: Reserved, not used + */ +struct dwc3_event_devt { + u32 one_bit:1; + u32 device_event:7; + u32 type:4; + u32 reserved15_12:4; + u32 event_info:9; + u32 reserved31_25:7; +} __packed; + +/** + * struct dwc3_event_gevt - Other Core Events + * @one_bit: indicates this is a non-endpoint event (not used) + * @device_event: indicates it's (0x03) Carkit or (0x04) I2C event. + * @phy_port_number: self-explanatory + * @reserved31_12: Reserved, not used. + */ +struct dwc3_event_gevt { + u32 one_bit:1; + u32 device_event:7; + u32 phy_port_number:4; + u32 reserved31_12:20; +} __packed; + +/** + * union dwc3_event - representation of Event Buffer contents + * @raw: raw 32-bit event + * @type: the type of the event + * @depevt: Device Endpoint Event + * @devt: Device Event + * @gevt: Global Event + */ +union dwc3_event { + u32 raw; + struct dwc3_event_type type; + struct dwc3_event_depevt depevt; + struct dwc3_event_devt devt; + struct dwc3_event_gevt gevt; +}; + +/** + * struct dwc3_gadget_ep_cmd_params - representation of endpoint command + * parameters + * @param2: third parameter + * @param1: second parameter + * @param0: first parameter + */ +struct dwc3_gadget_ep_cmd_params { + u32 param2; + u32 param1; + u32 param0; +}; + +/* + * DWC3 Features to be used as Driver Data + */ + +#define DWC3_HAS_PERIPHERAL BIT(0) +#define DWC3_HAS_XHCI BIT(1) +#define DWC3_HAS_OTG BIT(3) + +/* prototypes */ +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode); +void dwc3_set_mode(struct dwc3 *dwc, u32 mode); + +/* check whether we are on the DWC_usb3 core */ +static inline bool dwc3_is_usb3(struct dwc3 *dwc) +{ + return !(dwc->revision & DWC3_REVISION_IS_DWC31); +} + +/* check whether we are on the DWC_usb31 core */ +static inline bool dwc3_is_usb31(struct dwc3 *dwc) +{ + return !!(dwc->revision & DWC3_REVISION_IS_DWC31); +} + +bool dwc3_has_imod(struct dwc3 *dwc); + +int dwc3_event_buffers_setup(struct dwc3 *dwc); +void dwc3_event_buffers_cleanup(struct dwc3 *dwc); + +#if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_host_init(struct dwc3 *dwc); +#else +static inline int dwc3_host_init(struct dwc3 *dwc) +{ return 0; } +#endif + +#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_gadget_init(struct dwc3 *dwc); +void dwc3_gadget_exit(struct dwc3 *dwc); +int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode); +int dwc3_gadget_get_link_state(struct dwc3 *dwc); +int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state); +int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, + struct dwc3_gadget_ep_cmd_params *params); +int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param); +#else +static inline int dwc3_gadget_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_gadget_exit(struct dwc3 *dwc) +{ } +static inline int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) +{ return 0; } +static inline int dwc3_gadget_get_link_state(struct dwc3 *dwc) +{ return 0; } +static inline int dwc3_gadget_set_link_state(struct dwc3 *dwc, + enum dwc3_link_state state) +{ return 0; } + +static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, + int cmd, u32 param) +{ return 0; } +#endif + +#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_drd_init(struct dwc3 *dwc); +void dwc3_drd_exit(struct dwc3 *dwc); +void dwc3_otg_init(struct dwc3 *dwc); +void dwc3_otg_exit(struct dwc3 *dwc); +void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus); +void dwc3_otg_host_init(struct dwc3 *dwc); +#else +static inline int dwc3_drd_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_drd_exit(struct dwc3 *dwc) +{ } +static inline void dwc3_otg_init(struct dwc3 *dwc) +{ } +static inline void dwc3_otg_exit(struct dwc3 *dwc) +{ } +static inline void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus) +{ } +static inline void dwc3_otg_host_init(struct dwc3 *dwc) +{ } +#endif + +/* power management interface */ +#if !IS_ENABLED(CONFIG_USB_DWC3_HOST) +int dwc3_gadget_suspend(struct dwc3 *dwc); +int dwc3_gadget_resume(struct dwc3 *dwc); +void dwc3_gadget_process_pending_events(struct dwc3 *dwc); +#else +static inline int dwc3_gadget_suspend(struct dwc3 *dwc) +{ + return 0; +} + +static inline int dwc3_gadget_resume(struct dwc3 *dwc) +{ + return 0; +} + +static inline void dwc3_gadget_process_pending_events(struct dwc3 *dwc) +{ +} +#endif /* !IS_ENABLED(CONFIG_USB_DWC3_HOST) */ + +#if IS_ENABLED(CONFIG_USB_DWC3_ULPI) +int dwc3_ulpi_init(struct dwc3 *dwc); +void dwc3_ulpi_exit(struct dwc3 *dwc); +#else +static inline int dwc3_ulpi_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_ulpi_exit(struct dwc3 *dwc) +{ } +#endif + +#endif /* __DRIVERS_USB_DWC3_CORE_H */ diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h new file mode 100644 index 0000000000..4f75ab3505 --- /dev/null +++ b/drivers/usb/dwc3/debug.h @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * debug.h - DesignWare USB3 DRD Controller Debug Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi <balbi@ti.com>, + * Sebastian Andrzej Siewior <bigeasy@linutronix.de> + */ + +#ifndef __DWC3_DEBUG_H +#define __DWC3_DEBUG_H + +#include "core.h" + +/** + * dwc3_gadget_ep_cmd_string - returns endpoint command string + * @cmd: command code + */ +static inline const char * +dwc3_gadget_ep_cmd_string(u8 cmd) +{ + switch (cmd) { + case DWC3_DEPCMD_DEPSTARTCFG: + return "Start New Configuration"; + case DWC3_DEPCMD_ENDTRANSFER: + return "End Transfer"; + case DWC3_DEPCMD_UPDATETRANSFER: + return "Update Transfer"; + case DWC3_DEPCMD_STARTTRANSFER: + return "Start Transfer"; + case DWC3_DEPCMD_CLEARSTALL: + return "Clear Stall"; + case DWC3_DEPCMD_SETSTALL: + return "Set Stall"; + case DWC3_DEPCMD_GETEPSTATE: + return "Get Endpoint State"; + case DWC3_DEPCMD_SETTRANSFRESOURCE: + return "Set Endpoint Transfer Resource"; + case DWC3_DEPCMD_SETEPCONFIG: + return "Set Endpoint Configuration"; + default: + return "UNKNOWN command"; + } +} + +/** + * dwc3_gadget_generic_cmd_string - returns generic command string + * @cmd: command code + */ +static inline const char * +dwc3_gadget_generic_cmd_string(u8 cmd) +{ + switch (cmd) { + case DWC3_DGCMD_SET_LMP: + return "Set LMP"; + case DWC3_DGCMD_SET_PERIODIC_PAR: + return "Set Periodic Parameters"; + case DWC3_DGCMD_XMIT_FUNCTION: + return "Transmit Function Wake Device Notification"; + case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO: + return "Set Scratchpad Buffer Array Address Lo"; + case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI: + return "Set Scratchpad Buffer Array Address Hi"; + case DWC3_DGCMD_SELECTED_FIFO_FLUSH: + return "Selected FIFO Flush"; + case DWC3_DGCMD_ALL_FIFO_FLUSH: + return "All FIFO Flush"; + case DWC3_DGCMD_SET_ENDPOINT_NRDY: + return "Set Endpoint NRDY"; + case DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK: + return "Run SoC Bus Loopback Test"; + default: + return "UNKNOWN"; + } +} + +/** + * dwc3_gadget_link_string - returns link name + * @link_state: link state code + */ +static inline const char * +dwc3_gadget_link_string(enum dwc3_link_state link_state) +{ + switch (link_state) { + case DWC3_LINK_STATE_U0: + return "U0"; + case DWC3_LINK_STATE_U1: + return "U1"; + case DWC3_LINK_STATE_U2: + return "U2"; + case DWC3_LINK_STATE_U3: + return "U3"; + case DWC3_LINK_STATE_SS_DIS: + return "SS.Disabled"; + case DWC3_LINK_STATE_RX_DET: + return "RX.Detect"; + case DWC3_LINK_STATE_SS_INACT: + return "SS.Inactive"; + case DWC3_LINK_STATE_POLL: + return "Polling"; + case DWC3_LINK_STATE_RECOV: + return "Recovery"; + case DWC3_LINK_STATE_HRESET: + return "Hot Reset"; + case DWC3_LINK_STATE_CMPLY: + return "Compliance"; + case DWC3_LINK_STATE_LPBK: + return "Loopback"; + case DWC3_LINK_STATE_RESET: + return "Reset"; + case DWC3_LINK_STATE_RESUME: + return "Resume"; + default: + return "UNKNOWN link state\n"; + } +} + +/** + * dwc3_gadget_hs_link_string - returns highspeed and below link name + * @link_state: link state code + */ +static inline const char * +dwc3_gadget_hs_link_string(enum dwc3_link_state link_state) +{ + switch (link_state) { + case DWC3_LINK_STATE_U0: + return "On"; + case DWC3_LINK_STATE_U2: + return "Sleep"; + case DWC3_LINK_STATE_U3: + return "Suspend"; + case DWC3_LINK_STATE_SS_DIS: + return "Disconnected"; + case DWC3_LINK_STATE_RX_DET: + return "Early Suspend"; + case DWC3_LINK_STATE_RECOV: + return "Recovery"; + case DWC3_LINK_STATE_RESET: + return "Reset"; + case DWC3_LINK_STATE_RESUME: + return "Resume"; + default: + return "UNKNOWN link state\n"; + } +} + +/** + * dwc3_trb_type_string - returns TRB type as a string + * @type: the type of the TRB + */ +static inline const char *dwc3_trb_type_string(unsigned int type) +{ + switch (type) { + case DWC3_TRBCTL_NORMAL: + return "normal"; + case DWC3_TRBCTL_CONTROL_SETUP: + return "setup"; + case DWC3_TRBCTL_CONTROL_STATUS2: + return "status2"; + case DWC3_TRBCTL_CONTROL_STATUS3: + return "status3"; + case DWC3_TRBCTL_CONTROL_DATA: + return "data"; + case DWC3_TRBCTL_ISOCHRONOUS_FIRST: + return "isoc-first"; + case DWC3_TRBCTL_ISOCHRONOUS: + return "isoc"; + case DWC3_TRBCTL_LINK_TRB: + return "link"; + default: + return "UNKNOWN"; + } +} + +static inline const char *dwc3_ep0_state_string(enum dwc3_ep0_state state) +{ + switch (state) { + case EP0_UNCONNECTED: + return "Unconnected"; + case EP0_SETUP_PHASE: + return "Setup Phase"; + case EP0_DATA_PHASE: + return "Data Phase"; + case EP0_STATUS_PHASE: + return "Status Phase"; + default: + return "UNKNOWN"; + } +} + +/** + * dwc3_gadget_event_string - returns event name + * @event: the event code + */ +static inline const char * +dwc3_gadget_event_string(char *str, const struct dwc3_event_devt *event) +{ + enum dwc3_link_state state = event->event_info & DWC3_LINK_STATE_MASK; + + switch (event->type) { + case DWC3_DEVICE_EVENT_DISCONNECT: + sprintf(str, "Disconnect: [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_RESET: + sprintf(str, "Reset [%s]", dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_CONNECT_DONE: + sprintf(str, "Connection Done [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: + sprintf(str, "Link Change [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_WAKEUP: + sprintf(str, "WakeUp [%s]", dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_EOPF: + sprintf(str, "End-Of-Frame [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_SOF: + sprintf(str, "Start-Of-Frame [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_ERRATIC_ERROR: + sprintf(str, "Erratic Error [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_CMD_CMPL: + sprintf(str, "Command Complete [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_OVERFLOW: + sprintf(str, "Overflow [%s]", dwc3_gadget_link_string(state)); + break; + default: + sprintf(str, "UNKNOWN"); + } + + return str; +} + +static inline void dwc3_decode_get_status(__u8 t, __u16 i, __u16 l, char *str) +{ + switch (t & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + sprintf(str, "Get Interface Status(Intf = %d, Length = %d)", + i, l); + break; + case USB_RECIP_ENDPOINT: + sprintf(str, "Get Endpoint Status(ep%d%s)", + i & ~USB_DIR_IN, + i & USB_DIR_IN ? "in" : "out"); + break; + } +} + +static inline void dwc3_decode_set_clear_feature(__u8 t, __u8 b, __u16 v, + __u16 i, char *str) +{ + switch (t & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + sprintf(str, "%s Device Feature(%s%s)", + b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + ({char *s; + switch (v) { + case USB_DEVICE_SELF_POWERED: + s = "Self Powered"; + break; + case USB_DEVICE_REMOTE_WAKEUP: + s = "Remote Wakeup"; + break; + case USB_DEVICE_TEST_MODE: + s = "Test Mode"; + break; + case USB_DEVICE_U1_ENABLE: + s = "U1 Enable"; + break; + case USB_DEVICE_U2_ENABLE: + s = "U2 Enable"; + break; + case USB_DEVICE_LTM_ENABLE: + s = "LTM Enable"; + break; + default: + s = "UNKNOWN"; + } s; }), + v == USB_DEVICE_TEST_MODE ? + ({ char *s; + switch (i) { + case TEST_J: + s = ": TEST_J"; + break; + case TEST_K: + s = ": TEST_K"; + break; + case TEST_SE0_NAK: + s = ": TEST_SE0_NAK"; + break; + case TEST_PACKET: + s = ": TEST_PACKET"; + break; + case TEST_FORCE_EN: + s = ": TEST_FORCE_EN"; + break; + default: + s = ": UNKNOWN"; + } s; }) : ""); + break; + case USB_RECIP_INTERFACE: + sprintf(str, "%s Interface Feature(%s)", + b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + v == USB_INTRF_FUNC_SUSPEND ? + "Function Suspend" : "UNKNOWN"); + break; + case USB_RECIP_ENDPOINT: + sprintf(str, "%s Endpoint Feature(%s ep%d%s)", + b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + v == USB_ENDPOINT_HALT ? "Halt" : "UNKNOWN", + i & ~USB_DIR_IN, + i & USB_DIR_IN ? "in" : "out"); + break; + } +} + +static inline void dwc3_decode_set_address(__u16 v, char *str) +{ + sprintf(str, "Set Address(Addr = %02x)", v); +} + +static inline void dwc3_decode_get_set_descriptor(__u8 t, __u8 b, __u16 v, + __u16 i, __u16 l, char *str) +{ + sprintf(str, "%s %s Descriptor(Index = %d, Length = %d)", + b == USB_REQ_GET_DESCRIPTOR ? "Get" : "Set", + ({ char *s; + switch (v >> 8) { + case USB_DT_DEVICE: + s = "Device"; + break; + case USB_DT_CONFIG: + s = "Configuration"; + break; + case USB_DT_STRING: + s = "String"; + break; + case USB_DT_INTERFACE: + s = "Interface"; + break; + case USB_DT_ENDPOINT: + s = "Endpoint"; + break; + case USB_DT_DEVICE_QUALIFIER: + s = "Device Qualifier"; + break; + case USB_DT_OTHER_SPEED_CONFIG: + s = "Other Speed Config"; + break; + case USB_DT_INTERFACE_POWER: + s = "Interface Power"; + break; + case USB_DT_OTG: + s = "OTG"; + break; + case USB_DT_DEBUG: + s = "Debug"; + break; + case USB_DT_INTERFACE_ASSOCIATION: + s = "Interface Association"; + break; + case USB_DT_BOS: + s = "BOS"; + break; + case USB_DT_DEVICE_CAPABILITY: + s = "Device Capability"; + break; + case USB_DT_PIPE_USAGE: + s = "Pipe Usage"; + break; + case USB_DT_SS_ENDPOINT_COMP: + s = "SS Endpoint Companion"; + break; + case USB_DT_SSP_ISOC_ENDPOINT_COMP: + s = "SSP Isochronous Endpoint Companion"; + break; + default: + s = "UNKNOWN"; + break; + } s; }), v & 0xff, l); +} + + +static inline void dwc3_decode_get_configuration(__u16 l, char *str) +{ + sprintf(str, "Get Configuration(Length = %d)", l); +} + +static inline void dwc3_decode_set_configuration(__u8 v, char *str) +{ + sprintf(str, "Set Configuration(Config = %d)", v); +} + +static inline void dwc3_decode_get_intf(__u16 i, __u16 l, char *str) +{ + sprintf(str, "Get Interface(Intf = %d, Length = %d)", i, l); +} + +static inline void dwc3_decode_set_intf(__u8 v, __u16 i, char *str) +{ + sprintf(str, "Set Interface(Intf = %d, Alt.Setting = %d)", i, v); +} + +static inline void dwc3_decode_synch_frame(__u16 i, __u16 l, char *str) +{ + sprintf(str, "Synch Frame(Endpoint = %d, Length = %d)", i, l); +} + +static inline void dwc3_decode_set_sel(__u16 l, char *str) +{ + sprintf(str, "Set SEL(Length = %d)", l); +} + +static inline void dwc3_decode_set_isoch_delay(__u8 v, char *str) +{ + sprintf(str, "Set Isochronous Delay(Delay = %d ns)", v); +} + +/** + * dwc3_decode_ctrl - returns a string represetion of ctrl request + */ +static inline const char *dwc3_decode_ctrl(char *str, __u8 bRequestType, + __u8 bRequest, __u16 wValue, __u16 wIndex, __u16 wLength) +{ + switch (bRequest) { + case USB_REQ_GET_STATUS: + dwc3_decode_get_status(bRequestType, wIndex, wLength, str); + break; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + dwc3_decode_set_clear_feature(bRequestType, bRequest, wValue, + wIndex, str); + break; + case USB_REQ_SET_ADDRESS: + dwc3_decode_set_address(wValue, str); + break; + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_SET_DESCRIPTOR: + dwc3_decode_get_set_descriptor(bRequestType, bRequest, wValue, + wIndex, wLength, str); + break; + case USB_REQ_GET_CONFIGURATION: + dwc3_decode_get_configuration(wLength, str); + break; + case USB_REQ_SET_CONFIGURATION: + dwc3_decode_set_configuration(wValue, str); + break; + case USB_REQ_GET_INTERFACE: + dwc3_decode_get_intf(wIndex, wLength, str); + break; + case USB_REQ_SET_INTERFACE: + dwc3_decode_set_intf(wValue, wIndex, str); + break; + case USB_REQ_SYNCH_FRAME: + dwc3_decode_synch_frame(wIndex, wLength, str); + break; + case USB_REQ_SET_SEL: + dwc3_decode_set_sel(wLength, str); + break; + case USB_REQ_SET_ISOCH_DELAY: + dwc3_decode_set_isoch_delay(wValue, str); + break; + default: + sprintf(str, "%02x %02x %02x %02x %02x %02x %02x %02x", + bRequestType, bRequest, + cpu_to_le16(wValue) & 0xff, + cpu_to_le16(wValue) >> 8, + cpu_to_le16(wIndex) & 0xff, + cpu_to_le16(wIndex) >> 8, + cpu_to_le16(wLength) & 0xff, + cpu_to_le16(wLength) >> 8); + } + + return str; +} + +/** + * dwc3_ep_event_string - returns event name + * @event: then event code + */ +static inline const char * +dwc3_ep_event_string(char *str, const struct dwc3_event_depevt *event, + u32 ep0state) +{ + u8 epnum = event->endpoint_number; + size_t len; + int status; + int ret; + + ret = sprintf(str, "ep%d%s: ", epnum >> 1, + (epnum & 1) ? "in" : "out"); + if (ret < 0) + return "UNKNOWN"; + + status = event->status; + + switch (event->endpoint_event) { + case DWC3_DEPEVT_XFERCOMPLETE: + len = strlen(str); + sprintf(str + len, "Transfer Complete (%c%c%c)", + status & DEPEVT_STATUS_SHORT ? 'S' : 's', + status & DEPEVT_STATUS_IOC ? 'I' : 'i', + status & DEPEVT_STATUS_LST ? 'L' : 'l'); + + len = strlen(str); + + if (epnum <= 1) + sprintf(str + len, " [%s]", dwc3_ep0_state_string(ep0state)); + break; + case DWC3_DEPEVT_XFERINPROGRESS: + len = strlen(str); + + sprintf(str + len, "Transfer In Progress [%d] (%c%c%c)", + event->parameters, + status & DEPEVT_STATUS_SHORT ? 'S' : 's', + status & DEPEVT_STATUS_IOC ? 'I' : 'i', + status & DEPEVT_STATUS_LST ? 'M' : 'm'); + break; + case DWC3_DEPEVT_XFERNOTREADY: + len = strlen(str); + + sprintf(str + len, "Transfer Not Ready [%d]%s", + event->parameters, + status & DEPEVT_STATUS_TRANSFER_ACTIVE ? + " (Active)" : " (Not Active)"); + + /* Control Endpoints */ + if (epnum <= 1) { + int phase = DEPEVT_STATUS_CONTROL_PHASE(event->status); + + switch (phase) { + case DEPEVT_STATUS_CONTROL_DATA: + strcat(str, " [Data Phase]"); + break; + case DEPEVT_STATUS_CONTROL_STATUS: + strcat(str, " [Status Phase]"); + } + } + break; + case DWC3_DEPEVT_RXTXFIFOEVT: + strcat(str, "FIFO"); + break; + case DWC3_DEPEVT_STREAMEVT: + status = event->status; + + switch (status) { + case DEPEVT_STREAMEVT_FOUND: + sprintf(str + ret, " Stream %d Found", + event->parameters); + break; + case DEPEVT_STREAMEVT_NOTFOUND: + default: + strcat(str, " Stream Not Found"); + break; + } + + break; + case DWC3_DEPEVT_EPCMDCMPLT: + strcat(str, "Endpoint Command Complete"); + break; + default: + sprintf(str, "UNKNOWN"); + } + + return str; +} + +/** + * dwc3_gadget_event_type_string - return event name + * @event: the event code + */ +static inline const char *dwc3_gadget_event_type_string(u8 event) +{ + switch (event) { + case DWC3_DEVICE_EVENT_DISCONNECT: + return "Disconnect"; + case DWC3_DEVICE_EVENT_RESET: + return "Reset"; + case DWC3_DEVICE_EVENT_CONNECT_DONE: + return "Connect Done"; + case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: + return "Link Status Change"; + case DWC3_DEVICE_EVENT_WAKEUP: + return "Wake-Up"; + case DWC3_DEVICE_EVENT_HIBER_REQ: + return "Hibernation"; + case DWC3_DEVICE_EVENT_EOPF: + return "End of Periodic Frame"; + case DWC3_DEVICE_EVENT_SOF: + return "Start of Frame"; + case DWC3_DEVICE_EVENT_ERRATIC_ERROR: + return "Erratic Error"; + case DWC3_DEVICE_EVENT_CMD_CMPL: + return "Command Complete"; + case DWC3_DEVICE_EVENT_OVERFLOW: + return "Overflow"; + default: + return "UNKNOWN"; + } +} + +static inline const char *dwc3_decode_event(char *str, u32 event, u32 ep0state) +{ + const union dwc3_event evt = (union dwc3_event) event; + + if (evt.type.is_devspec) + return dwc3_gadget_event_string(str, &evt.devt); + else + return dwc3_ep_event_string(str, &evt.depevt, ep0state); +} + +static inline const char *dwc3_ep_cmd_status_string(int status) +{ + switch (status) { + case -ETIMEDOUT: + return "Timed Out"; + case 0: + return "Successful"; + case DEPEVT_TRANSFER_NO_RESOURCE: + return "No Resource"; + case DEPEVT_TRANSFER_BUS_EXPIRY: + return "Bus Expiry"; + default: + return "UNKNOWN"; + } +} + +static inline const char *dwc3_gadget_generic_cmd_status_string(int status) +{ + switch (status) { + case -ETIMEDOUT: + return "Timed Out"; + case 0: + return "Successful"; + case 1: + return "Error"; + default: + return "UNKNOWN"; + } +} + + +#ifdef CONFIG_DEBUG_FS +extern void dwc3_debugfs_init(struct dwc3 *); +extern void dwc3_debugfs_exit(struct dwc3 *); +#else +static inline void dwc3_debugfs_init(struct dwc3 *d) +{ } +static inline void dwc3_debugfs_exit(struct dwc3 *d) +{ } +#endif +#endif /* __DWC3_DEBUG_H */ diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c new file mode 100644 index 0000000000..d5daa7f19e --- /dev/null +++ b/drivers/usb/dwc3/host.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * host.c - DesignWare USB3 DRD Controller Host Glue + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi <balbi@ti.com>, + */ + +#include <common.h> +#include <driver.h> +#include <init.h> + +#include "core.h" + +int dwc3_host_init(struct dwc3 *dwc) +{ + struct resource *io; + struct device_d *dev = dwc->dev; + + io = dev_get_resource(dev, IORESOURCE_MEM, 0); + if (IS_ERR(io)) { + dev_err(dev, "Failed to get IORESOURCE_MEM\n"); + return PTR_ERR(io); + } + + dwc->xhci = add_generic_device("xHCI", DEVICE_ID_DYNAMIC, NULL, + io->start, resource_size(io), + IORESOURCE_MEM, NULL); + if (!dwc->xhci) { + dev_err(dev, "Failed to register xHCI device\n"); + return -ENODEV; + } + + return 0; +} diff --git a/drivers/usb/dwc3/io.h b/drivers/usb/dwc3/io.h new file mode 100644 index 0000000000..f87b173e90 --- /dev/null +++ b/drivers/usb/dwc3/io.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * io.h - DesignWare USB3 DRD IO Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi <balbi@ti.com>, + * Sebastian Andrzej Siewior <bigeasy@linutronix.de> + */ + +#ifndef __DRIVERS_USB_DWC3_IO_H +#define __DRIVERS_USB_DWC3_IO_H + +#include <io.h> +#include "core.h" + +static inline u32 dwc3_readl(void __iomem *base, u32 offset) +{ + u32 value; + + /* + * We requested the mem region starting from the Globals address + * space, see dwc3_probe in core.c. + * However, the offsets are given starting from xHCI address space. + */ + value = readl(base + offset - DWC3_GLOBALS_REGS_START); + + return value; +} + +static inline void dwc3_writel(void __iomem *base, u32 offset, u32 value) +{ + /* + * We requested the mem region starting from the Globals address + * space, see dwc3_probe in core.c. + * However, the offsets are given starting from xHCI address space. + */ + writel(value, base + offset - DWC3_GLOBALS_REGS_START); +} + +#endif /* __DRIVERS_USB_DWC3_IO_H */ diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 2b808cc875..32a6ccd5cd 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -23,16 +23,49 @@ #include "xhci.h" + +static struct xhci_input_control_ctx * +xhci_get_input_control_ctx(struct xhci_container_ctx *ctx) +{ + if (ctx->type != XHCI_CTX_TYPE_INPUT) + return NULL; + + return (struct xhci_input_control_ctx *)ctx->bytes; +} + +static struct xhci_slot_ctx *xhci_get_slot_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx) +{ + if (ctx->type == XHCI_CTX_TYPE_DEVICE) + return (struct xhci_slot_ctx *)ctx->bytes; + + return (struct xhci_slot_ctx *) + (ctx->bytes + HCC_CTX_SIZE(xhci->hcc_params)); +} + +static struct xhci_ep_ctx *xhci_get_ep_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx, + unsigned int ep_index) +{ + /* increment ep index by offset of start of ep ctx array */ + ep_index++; + if (ctx->type == XHCI_CTX_TYPE_INPUT) + ep_index++; + + return (struct xhci_ep_ctx *) + (ctx->bytes + (ep_index * HCC_CTX_SIZE(xhci->hcc_params))); +} + /* * xHCI ring handling */ static int xhci_ring_is_last_trb(struct xhci_ring *ring, union xhci_trb *trb) { - if (ring->type == TYPE_EVENT) - return trb == &ring->trbs[NUM_EVENT_TRBS]; - else - return TRB_TYPE_LINK(le32_to_cpu(trb->link.control)); + if (ring->type == TYPE_EVENT) + return trb == &ring->trbs[NUM_EVENT_TRBS]; + else + return TRB_TYPE_LINK(le32_to_cpu(trb->link.control)); } static int xhci_ring_increment(struct xhci_ring *ring, bool enqueue) @@ -41,7 +74,7 @@ static int xhci_ring_increment(struct xhci_ring *ring, bool enqueue) (*queue)++; - if (!xhci_ring_is_last_trb(ring, *queue)) + if (!xhci_ring_is_last_trb(ring, *queue)) return 0; if (ring->type == TYPE_EVENT) { @@ -139,15 +172,15 @@ static unsigned int xhci_get_endpoint_index(u8 epaddress, u8 epattributes) { u8 epnum = epaddress & USB_ENDPOINT_NUMBER_MASK; u8 xfer = epattributes & USB_ENDPOINT_XFERTYPE_MASK; - unsigned int index; + unsigned int index; - if (xfer == USB_ENDPOINT_XFER_CONTROL) - index = (unsigned int)(epnum * 2); - else - index = (unsigned int)(epnum * 2) + - ((epaddress & USB_DIR_IN) ? 1 : 0) - 1; + if (xfer == USB_ENDPOINT_XFER_CONTROL) + index = (unsigned int)(epnum * 2); + else + index = (unsigned int)(epnum * 2) + + ((epaddress & USB_DIR_IN) ? 1 : 0) - 1; - return index; + return index; } static u8 xhci_get_endpoint_type(u8 epaddress, u8 epattributes) @@ -341,15 +374,15 @@ int xhci_issue_command(struct xhci_hcd *xhci, union xhci_trb *trb) static void xhci_set_event_dequeue(struct xhci_hcd *xhci) { - u64 reg64; + u64 reg64; - reg64 = xhci_read_64(&xhci->ir_set->erst_dequeue); - reg64 &= ERST_PTR_MASK; - /* + reg64 = xhci_read_64(&xhci->ir_set->erst_dequeue); + reg64 &= ERST_PTR_MASK; + /* * Don't clear the EHB bit (which is RW1C) because - * there might be more events to service. - */ - reg64 &= ~ERST_EHB; + * there might be more events to service. + */ + reg64 &= ~ERST_EHB; reg64 |= (dma_addr_t)xhci->event_ring.dequeue & ~(dma_addr_t)ERST_PTR_MASK; @@ -426,29 +459,48 @@ static struct xhci_virtual_device *xhci_find_virtdev(struct xhci_hcd *xhci, return NULL; } +static struct xhci_container_ctx * +xhci_alloc_container_ctx(struct xhci_hcd *xhci, int type) +{ + struct xhci_container_ctx *ctx; + + if ((type != XHCI_CTX_TYPE_DEVICE) && (type != XHCI_CTX_TYPE_INPUT)) + return NULL; + + ctx = xzalloc(sizeof(*ctx)); + ctx->type = type; + ctx->size = HCC_64BYTE_CONTEXT(xhci->hcc_params) ? 2048 : 1024; + if (type == XHCI_CTX_TYPE_INPUT) + ctx->size += HCC_CTX_SIZE(xhci->hcc_params); + + ctx->bytes = dma_alloc_coherent(ctx->size, &ctx->dma); + if (WARN_ON(!ctx->bytes)) { + kfree(ctx); + return NULL; + } + return ctx; +} + +static void xhci_free_container_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx) +{ + if (!ctx) + return; + dma_free_coherent(ctx->bytes, ctx->dma, ctx->size); + kfree(ctx); +} + static struct xhci_virtual_device *xhci_alloc_virtdev(struct xhci_hcd *xhci, struct usb_device *udev) { struct xhci_virtual_device *vdev; - size_t sz_ctx, sz_ictx, sz_dctx; - void *p; vdev = xzalloc(sizeof(*vdev)); vdev->udev = udev; list_add_tail(&vdev->list, &xhci->vdev_list); - sz_ctx = HCC_64BYTE_CONTEXT(xhci->hcc_params) ? 2048 : 1024; - /* Device Context: 64B aligned */ - sz_dctx = ALIGN(sz_ctx, 64); - /* Input Control Context: 64B aligned */ - sz_ictx = ALIGN(sz_ctx + HCC_CTX_SIZE(xhci->hcc_params), 64); - - vdev->dma_size = sz_ictx + sz_dctx; - p = vdev->dma = dma_alloc_coherent(vdev->dma_size, DMA_ADDRESS_BROKEN); - memset(vdev->dma, 0, vdev->dma_size); - - vdev->out_ctx = p; p += sz_dctx; - vdev->in_ctx = p; p += sz_ictx; + vdev->out_ctx = xhci_alloc_container_ctx(xhci, XHCI_CTX_TYPE_DEVICE); + vdev->in_ctx = xhci_alloc_container_ctx(xhci, XHCI_CTX_TYPE_INPUT); return vdev; } @@ -463,7 +515,8 @@ static void xhci_free_virtdev(struct xhci_virtual_device *vdev) xhci_put_endpoint_ring(xhci, vdev->ep[i]); list_del(&vdev->list); - dma_free_coherent(vdev->dma, 0, vdev->dma_size); + xhci_free_container_ctx(xhci, vdev->out_ctx); + xhci_free_container_ctx(xhci, vdev->in_ctx); free(vdev); } @@ -487,26 +540,43 @@ static int xhci_virtdev_issue_transfer(struct xhci_virtual_device *vdev, static void xhci_virtdev_zero_in_ctx(struct xhci_virtual_device *vdev) { + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + struct xhci_input_control_ctx *in_icc; + struct xhci_slot_ctx *in_slot; + struct xhci_ep_ctx *in_ep; int i; - /* When a device's add flag and drop flag are zero, any subsequent - * configure endpoint command will leave that endpoint's state - * untouched. Make sure we don't leave any old state in the input - * endpoint contexts. - */ - vdev->in_ctx->icc.drop_flags = 0; - vdev->in_ctx->icc.add_flags = 0; - vdev->in_ctx->slot.dev_info &= cpu_to_le32(~LAST_CTX_MASK); - /* Endpoint 0 is always valid */ - vdev->in_ctx->slot.dev_info |= cpu_to_le32(LAST_CTX(1)); + in_icc = xhci_get_input_control_ctx(vdev->in_ctx); + in_slot = xhci_get_slot_ctx(xhci, vdev->in_ctx); + + /* When a device's add flag and drop flag are zero, any subsequent + * configure endpoint command will leave that endpoint's state + * untouched. Make sure we don't leave any old state in the input + * endpoint contexts. + */ + in_icc->drop_flags = 0; + in_icc->add_flags = 0; + in_slot->dev_info &= cpu_to_le32(~LAST_CTX_MASK); + /* Endpoint 0 is always valid */ + in_slot->dev_info |= cpu_to_le32(LAST_CTX(1)); for (i = 1; i < 31; i++) { - vdev->in_ctx->ep[i].ep_info = 0; - vdev->in_ctx->ep[i].ep_info2 = 0; - vdev->in_ctx->ep[i].deq = 0; - vdev->in_ctx->ep[i].tx_info = 0; + in_ep = xhci_get_ep_ctx(xhci, vdev->in_ctx, i); + + in_ep->ep_info = 0; + in_ep->ep_info2 = 0; + in_ep->deq = 0; + in_ep->tx_info = 0; } } +static void xhci_init_event_cmd_trb(union xhci_trb *trb, + u64 cmd_trb, u32 status, u32 flags) +{ + xhci_write_64(cmd_trb, &trb->event_cmd.cmd_trb); + writel(status, &trb->event_cmd.status); + writel(flags, &trb->event_cmd.flags); +} + static int xhci_virtdev_disable_slot(struct xhci_virtual_device *vdev) { struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); @@ -514,15 +584,17 @@ static int xhci_virtdev_disable_slot(struct xhci_virtual_device *vdev) int ret; /* Issue Disable Slot Command */ - memset(&trb, 0, sizeof(union xhci_trb)); - trb.event_cmd.flags = TRB_TYPE(TRB_DISABLE_SLOT) | - SLOT_ID_FOR_TRB(vdev->slot_id); + xhci_init_event_cmd_trb(&trb, + 0, + 0, + TRB_TYPE(TRB_DISABLE_SLOT) | + SLOT_ID_FOR_TRB(vdev->slot_id)); xhci_print_trb(xhci, &trb, "Request DisableSlot"); xhci_issue_command(xhci, &trb); ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); xhci_print_trb(xhci, &trb, "Response DisableSlot"); - /* Clear Device Context Base Address Array */ + /* Clear Device Context Base Address Array */ xhci->dcbaa[vdev->slot_id] = 0; return ret; @@ -536,8 +608,10 @@ static int xhci_virtdev_enable_slot(struct xhci_virtual_device *vdev) int ret; /* Issue Enable Slot Command */ - memset(&trb, 0, sizeof(union xhci_trb)); - trb.event_cmd.flags = TRB_TYPE(TRB_ENABLE_SLOT); + xhci_init_event_cmd_trb(&trb, + 0, + 0, + TRB_TYPE(TRB_ENABLE_SLOT)); xhci_print_trb(xhci, &trb, "Request EnableSlot"); xhci_issue_command(xhci, &trb); ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); @@ -559,42 +633,47 @@ static int xhci_virtdev_enable_slot(struct xhci_virtual_device *vdev) int xhci_virtdev_reset(struct xhci_virtual_device *vdev) { struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + struct xhci_slot_ctx *out_slot; union xhci_trb trb; int ret; + out_slot = xhci_get_slot_ctx(xhci, vdev->out_ctx); + /* If device is not setup, there is no point in resetting it */ - if (GET_SLOT_STATE(le32_to_cpu(vdev->out_ctx->slot.dev_state)) == + if (GET_SLOT_STATE(le32_to_cpu(out_slot->dev_state)) == SLOT_STATE_DISABLED) - return 0; + return 0; - memset(&trb, 0, sizeof(union xhci_trb)); - trb.event_cmd.flags = TRB_TYPE(TRB_RESET_DEV) | - SLOT_ID_FOR_TRB(vdev->slot_id); + xhci_init_event_cmd_trb(&trb, + 0, + 0, + TRB_TYPE(TRB_RESET_DEV) | + SLOT_ID_FOR_TRB(vdev->slot_id)); xhci_print_trb(xhci, &trb, "Request Reset"); xhci_issue_command(xhci, &trb); ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); xhci_print_trb(xhci, &trb, "Response Reset"); - /* + /* * The Reset Device command can't fail, according to the 0.95/0.96 spec, - * unless we tried to reset a slot ID that wasn't enabled, - * or the device wasn't in the addressed or configured state. - */ - switch (GET_COMP_CODE(trb.event_cmd.status)) { - case COMP_CMD_ABORT: - case COMP_CMD_STOP: - dev_warn(xhci->dev, "Timeout waiting for reset device command\n"); - ret = -ETIMEDOUT; + * unless we tried to reset a slot ID that wasn't enabled, + * or the device wasn't in the addressed or configured state. + */ + switch (GET_COMP_CODE(trb.event_cmd.status)) { + case COMP_CMD_ABORT: + case COMP_CMD_STOP: + dev_warn(xhci->dev, "Timeout waiting for reset device command\n"); + ret = -ETIMEDOUT; + break; + case COMP_EBADSLT: /* 0.95 completion code for bad slot ID */ + case COMP_CTX_STATE: /* 0.96 completion code for same thing */ + /* Don't treat this as an error. May change my mind later. */ + ret = 0; + case COMP_SUCCESS: break; - case COMP_EBADSLT: /* 0.95 completion code for bad slot ID */ - case COMP_CTX_STATE: /* 0.96 completion code for same thing */ - /* Don't treat this as an error. May change my mind later. */ - ret = 0; - case COMP_SUCCESS: - break; - default: - ret = -EINVAL; - } + default: + ret = -EINVAL; + } return ret; } @@ -608,31 +687,38 @@ static int xhci_virtdev_update_hub_device(struct xhci_virtual_device *vdev, { struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); struct usb_hub_descriptor *desc = buffer; + struct xhci_input_control_ctx *in_icc; + struct xhci_slot_ctx *in_slot, *out_slot; union xhci_trb trb; u32 dev_info, dev_info2, tt_info; u8 maxchild; u16 hubchar; + u32 flags; int ret; + out_slot = xhci_get_slot_ctx(xhci, vdev->out_ctx); + /* Need at least first byte of wHubCharacteristics */ if (length < 4) return 0; /* Skip already configured hub device */ - if (vdev->out_ctx->slot.dev_info & DEV_HUB) + if (out_slot->dev_info & DEV_HUB) return 0; maxchild = desc->bNbrPorts; hubchar = le16_to_cpu(desc->wHubCharacteristics); + in_slot = xhci_get_slot_ctx(xhci, vdev->in_ctx); + in_icc = xhci_get_input_control_ctx(vdev->in_ctx); + /* update slot context */ - memcpy(&vdev->in_ctx->slot, &vdev->out_ctx->slot, - sizeof(struct xhci_slot_ctx)); - vdev->in_ctx->icc.add_flags |= cpu_to_le32(SLOT_FLAG); - vdev->in_ctx->icc.drop_flags = 0; - vdev->in_ctx->slot.dev_state = 0; - dev_info = le32_to_cpu(vdev->in_ctx->slot.dev_info); - dev_info2 = le32_to_cpu(vdev->in_ctx->slot.dev_info2); - tt_info = le32_to_cpu(vdev->in_ctx->slot.tt_info); + memcpy(in_slot, out_slot, sizeof(struct xhci_slot_ctx)); + in_icc->add_flags |= cpu_to_le32(SLOT_FLAG); + in_icc->drop_flags = 0; + in_slot->dev_state = 0; + dev_info = le32_to_cpu(in_slot->dev_info); + dev_info2 = le32_to_cpu(in_slot->dev_info2); + tt_info = le32_to_cpu(in_slot->tt_info); dev_info |= DEV_HUB; /* HS Multi-TT in bDeviceProtocol */ @@ -651,21 +737,23 @@ static int xhci_virtdev_update_hub_device(struct xhci_virtual_device *vdev, if (xhci->hci_version < 0x100 || vdev->udev->speed == USB_SPEED_HIGH) { u32 think_time = (hubchar & HUB_CHAR_TTTT) >> 5; - tt_info |= TT_THINK_TIME(think_time); + tt_info |= TT_THINK_TIME(think_time); } - } - vdev->in_ctx->slot.dev_info = cpu_to_le32(dev_info); - vdev->in_ctx->slot.dev_info2 = cpu_to_le32(dev_info2); - vdev->in_ctx->slot.tt_info = cpu_to_le32(tt_info); - - /* Issue Configure Endpoint or Evaluate Context Command */ - memset(&trb, 0, sizeof(union xhci_trb)); - xhci_write_64((dma_addr_t)vdev->in_ctx, &trb.event_cmd.cmd_trb); - trb.event_cmd.flags = SLOT_ID_FOR_TRB(vdev->slot_id); + } + in_slot->dev_info = cpu_to_le32(dev_info); + in_slot->dev_info2 = cpu_to_le32(dev_info2); + in_slot->tt_info = cpu_to_le32(tt_info); + + /* Issue Configure Endpoint or Evaluate Context Command */ + flags = SLOT_ID_FOR_TRB(vdev->slot_id); if (xhci->hci_version > 0x95) - trb.event_cmd.flags |= TRB_TYPE(TRB_CONFIG_EP); + flags |= TRB_TYPE(TRB_CONFIG_EP); else - trb.event_cmd.flags |= TRB_TYPE(TRB_EVAL_CONTEXT); + flags |= TRB_TYPE(TRB_EVAL_CONTEXT); + xhci_init_event_cmd_trb(&trb, + vdev->in_ctx->dma, + 0, + flags); xhci_print_trb(xhci, &trb, "Request ConfigureEndpoint"); xhci_issue_command(xhci, &trb); ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); @@ -696,6 +784,8 @@ static int xhci_virtdev_update_hub_status(struct xhci_virtual_device *vhub, static int xhci_virtdev_configure(struct xhci_virtual_device *vdev, int config) { struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + struct xhci_input_control_ctx *in_icc; + struct xhci_slot_ctx *in_slot; struct usb_device *udev = vdev->udev; union xhci_trb trb; u32 add_flags = 0, last_ctx; @@ -712,10 +802,12 @@ static int xhci_virtdev_configure(struct xhci_virtual_device *vdev, int config) ep->bmAttributes); u8 epi = xhci_get_endpoint_index(ep->bEndpointAddress, ep->bmAttributes); - struct xhci_ep_ctx *ctx = &vdev->in_ctx->ep[epi]; + struct xhci_ep_ctx *ctx; u32 mps, interval, mult, esit, max_packet, max_burst; u32 ep_info, ep_info2, tx_info; + ctx = xhci_get_ep_ctx(xhci, vdev->in_ctx, epi); + vdev->ep[epi] = xhci_get_endpoint_ring(xhci); if (!vdev->ep[epi]) return -ENOMEM; @@ -782,25 +874,29 @@ static int xhci_virtdev_configure(struct xhci_virtual_device *vdev, int config) last_ctx = fls(add_flags) - 1; - /* See section 4.6.6 - A0 = 1; A1 = D0 = D1 = 0 */ - vdev->in_ctx->icc.add_flags = cpu_to_le32(add_flags); - vdev->in_ctx->icc.add_flags |= cpu_to_le32(SLOT_FLAG); - vdev->in_ctx->icc.add_flags &= cpu_to_le32(~EP0_FLAG); - vdev->in_ctx->icc.drop_flags &= cpu_to_le32(~(SLOT_FLAG | EP0_FLAG)); + in_slot = xhci_get_slot_ctx(xhci, vdev->in_ctx); + in_icc = xhci_get_input_control_ctx(vdev->in_ctx); + + /* See section 4.6.6 - A0 = 1; A1 = D0 = D1 = 0 */ + in_icc->add_flags = cpu_to_le32(add_flags); + in_icc->add_flags |= cpu_to_le32(SLOT_FLAG); + in_icc->add_flags &= cpu_to_le32(~EP0_FLAG); + in_icc->drop_flags &= cpu_to_le32(~(SLOT_FLAG | EP0_FLAG)); /* Don't issue the command if there's no endpoints to update. */ - if (vdev->in_ctx->icc.add_flags == cpu_to_le32(SLOT_FLAG) && - vdev->in_ctx->icc.drop_flags == 0) + if (in_icc->add_flags == cpu_to_le32(SLOT_FLAG) && + in_icc->drop_flags == 0) return 0; - vdev->in_ctx->slot.dev_info &= cpu_to_le32(~LAST_CTX_MASK); - vdev->in_ctx->slot.dev_info |= cpu_to_le32(LAST_CTX(last_ctx)); + in_slot->dev_info &= cpu_to_le32(~LAST_CTX_MASK); + in_slot->dev_info |= cpu_to_le32(LAST_CTX(last_ctx)); - /* Issue Configure Endpoint Command */ - memset(&trb, 0, sizeof(union xhci_trb)); - xhci_write_64((dma_addr_t)vdev->in_ctx, &trb.event_cmd.cmd_trb); - trb.event_cmd.flags = TRB_TYPE(TRB_CONFIG_EP) | - SLOT_ID_FOR_TRB(vdev->slot_id); + /* Issue Configure Endpoint Command */ + xhci_init_event_cmd_trb(&trb, + vdev->in_ctx->dma, + 0, + TRB_TYPE(TRB_CONFIG_EP) | + SLOT_ID_FOR_TRB(vdev->slot_id)); xhci_print_trb(xhci, &trb, "Request ConfigureEndpoint"); xhci_issue_command(xhci, &trb); ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); @@ -816,11 +912,12 @@ static int xhci_virtdev_deconfigure(struct xhci_virtual_device *vdev) union xhci_trb trb; int ret; - /* Issue Deconfigure Endpoint Command */ - memset(&trb, 0, sizeof(union xhci_trb)); - xhci_write_64((dma_addr_t)vdev->in_ctx, &trb.event_cmd.cmd_trb); - trb.event_cmd.flags = TRB_TYPE(TRB_CONFIG_EP) | TRB_DC | - SLOT_ID_FOR_TRB(vdev->slot_id); + /* Issue Deconfigure Endpoint Command */ + xhci_init_event_cmd_trb(&trb, + vdev->in_ctx->dma, + 0, + TRB_TYPE(TRB_CONFIG_EP) | TRB_DC | + SLOT_ID_FOR_TRB(vdev->slot_id)); xhci_print_trb(xhci, &trb, "Request DeconfigureEndpoint"); xhci_issue_command(xhci, &trb); ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); @@ -834,23 +931,30 @@ static int xhci_virtdev_init(struct xhci_virtual_device *vdev) { struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); struct usb_device *top_dev; + struct xhci_slot_ctx *in_slot; + struct xhci_ep_ctx *in_ep; int max_packets; u32 route = 0, dev_info, dev_info2, tt_info, ep_info2, tx_info; bool on_hs_hub = false; int hs_slot_id = 0; + in_ep = xhci_get_ep_ctx(xhci, vdev->in_ctx, 0); + in_slot = xhci_get_slot_ctx(xhci, vdev->in_ctx); + /* * Find the root hub port this device is under, also determine SlotID * of possible external HS hub a LS/FS device could be connected to. */ for (top_dev = vdev->udev; top_dev->parent && top_dev->parent->parent; top_dev = top_dev->parent) { - if (top_dev->parent->descriptor->bDeviceClass == USB_CLASS_HUB) - route = (route << 4) | (top_dev->portnr & 0xf); - if (top_dev->parent->descriptor->bDeviceClass == USB_CLASS_HUB && - top_dev->parent->speed != USB_SPEED_LOW && + if (top_dev->parent->descriptor->bDeviceClass != USB_CLASS_HUB) + continue; + + route = (route << 4) | (top_dev->portnr & 0xf); + + if (top_dev->parent->speed != USB_SPEED_LOW && top_dev->parent->speed != USB_SPEED_FULL) { - on_hs_hub |= true; + on_hs_hub = true; if (!hs_slot_id) { struct xhci_virtual_device *vhub = xhci_find_virtdev(xhci, top_dev->parent); @@ -893,9 +997,9 @@ static int xhci_virtdev_init(struct xhci_virtual_device *vdev) tt_info |= (top_dev->portnr << 8) | hs_slot_id; } - vdev->in_ctx->slot.dev_info = cpu_to_le32(dev_info); - vdev->in_ctx->slot.dev_info2 = cpu_to_le32(dev_info2); - vdev->in_ctx->slot.tt_info = cpu_to_le32(tt_info); + in_slot->dev_info = cpu_to_le32(dev_info); + in_slot->dev_info2 = cpu_to_le32(dev_info2); + in_slot->tt_info = cpu_to_le32(tt_info); /* 4.3.3 4) Initalize Transfer Ring */ vdev->ep[0] = xhci_get_endpoint_ring(xhci); @@ -907,13 +1011,13 @@ static int xhci_virtdev_init(struct xhci_virtual_device *vdev) ep_info2 = EP_TYPE(CTRL_EP) | MAX_BURST(0) | ERROR_COUNT(3); ep_info2 |= MAX_PACKET(max_packets); tx_info = AVG_TRB_LENGTH_FOR_EP(8); - vdev->in_ctx->ep[0].ep_info2 = cpu_to_le32(ep_info2); - vdev->in_ctx->ep[0].tx_info = cpu_to_le32(tx_info); - vdev->in_ctx->ep[0].deq = cpu_to_le64((dma_addr_t)vdev->ep[0]->enqueue | - vdev->ep[0]->cycle_state); + in_ep->ep_info2 = cpu_to_le32(ep_info2); + in_ep->tx_info = cpu_to_le32(tx_info); + in_ep->deq = cpu_to_le64((dma_addr_t)vdev->ep[0]->enqueue | + vdev->ep[0]->cycle_state); - /* 4.3.3 6+7) Initalize and Set Device Context Base Address Array */ - xhci->dcbaa[vdev->slot_id] = cpu_to_le64((dma_addr_t)vdev->out_ctx); + /* 4.3.3 6+7) Initalize and Set Device Context Base Address Array */ + xhci->dcbaa[vdev->slot_id] = cpu_to_le64(vdev->out_ctx->dma); return 0; } @@ -922,43 +1026,53 @@ static int xhci_virtdev_setup(struct xhci_virtual_device *vdev, enum xhci_setup_dev setup) { struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + static struct xhci_input_control_ctx *in_icc; + struct xhci_slot_ctx *in_slot; + struct xhci_ep_ctx *in_ep; union xhci_trb trb; + u32 flags; int ret; + in_slot = xhci_get_slot_ctx(xhci, vdev->in_ctx); + in_icc = xhci_get_input_control_ctx(vdev->in_ctx); + /* * If this is the first Set Address since device * plug-in then initialize Slot Context */ - if (!vdev->in_ctx->slot.dev_info) + if (!in_slot->dev_info) xhci_virtdev_init(vdev); else { + in_ep = xhci_get_ep_ctx(xhci, vdev->in_ctx, 0); + /* Otherwise, update Control Ring Dequeue pointer */ - vdev->in_ctx->ep[0].deq = - cpu_to_le64((dma_addr_t)vdev->ep[0]->enqueue | - vdev->ep[0]->cycle_state); + in_ep->deq = cpu_to_le64((dma_addr_t)vdev->ep[0]->enqueue | + vdev->ep[0]->cycle_state); /* * FS devices have MaxPacketSize0 of 8 or 64, we start * with 64. If assumtion was wrong, fix it up here. */ if (vdev->udev->speed == USB_SPEED_FULL && vdev->udev->maxpacketsize == PACKET_SIZE_8) { - u32 info = le32_to_cpu(vdev->in_ctx->ep[0].ep_info2); + u32 info = le32_to_cpu(in_ep->ep_info2); info &= ~MAX_PACKET_MASK; info |= MAX_PACKET(8); - vdev->in_ctx->ep[0].ep_info2 = cpu_to_le32(info); + in_ep->ep_info2 = cpu_to_le32(info); } } - vdev->in_ctx->icc.add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); - vdev->in_ctx->icc.drop_flags = 0; + in_icc->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); + in_icc->drop_flags = 0; - /* Issue Address Device Command */ - memset(&trb, 0, sizeof(union xhci_trb)); - xhci_write_64((dma_addr_t)vdev->in_ctx, &trb.event_cmd.cmd_trb); - trb.event_cmd.flags = TRB_TYPE(TRB_ADDR_DEV) | + /* Issue Address Device Command */ + flags = TRB_TYPE(TRB_ADDR_DEV) | SLOT_ID_FOR_TRB(vdev->slot_id); if (setup == SETUP_CONTEXT_ONLY) - trb.event_cmd.flags |= TRB_BSR; + flags |= TRB_BSR; + xhci_init_event_cmd_trb(&trb, + vdev->in_ctx->dma, + 0, + flags); xhci_print_trb(xhci, &trb, "Request AddressDevice"); xhci_issue_command(xhci, &trb); ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); @@ -970,12 +1084,12 @@ static int xhci_virtdev_setup(struct xhci_virtual_device *vdev, static int xhci_virtdev_set_address(struct xhci_virtual_device *vdev) { - return xhci_virtdev_setup(vdev, SETUP_CONTEXT_ADDRESS); + return xhci_virtdev_setup(vdev, SETUP_CONTEXT_ADDRESS); } static int xhci_virtdev_enable(struct xhci_virtual_device *vdev) { - return xhci_virtdev_setup(vdev, SETUP_CONTEXT_ONLY); + return xhci_virtdev_setup(vdev, SETUP_CONTEXT_ONLY); } static int xhci_virtdev_attach(struct xhci_hcd *xhci, struct usb_device *udev) @@ -1008,45 +1122,57 @@ static int xhci_submit_normal(struct usb_device *udev, unsigned long pipe, { struct usb_host *host = udev->host; struct xhci_hcd *xhci = to_xhci_hcd(host); + enum dma_data_direction dma_direction; struct xhci_virtual_device *vdev; + struct xhci_slot_ctx *out_slot; + dma_addr_t buffer_dma; union xhci_trb trb; - u8 epaddr = (usb_pipein(pipe) ? USB_DIR_IN : USB_DIR_OUT) | - usb_pipeendpoint(pipe); - u8 epi = xhci_get_endpoint_index(epaddr, usb_pipetype(pipe)); + u8 epaddr = usb_pipeendpoint(pipe); + u8 epi; + u32 flags = TRB_TYPE(TRB_NORMAL) | TRB_IOC; int ret; + if (usb_pipein(pipe)) { + epaddr |= USB_DIR_IN; + flags |= TRB_ISP; + dma_direction = DMA_FROM_DEVICE; + } else { + epaddr |= USB_DIR_OUT; + dma_direction = DMA_TO_DEVICE; + } + + epi = xhci_get_endpoint_index(epaddr, usb_pipetype(pipe)); vdev = xhci_find_virtdev(xhci, udev); if (!vdev) return -ENODEV; + out_slot = xhci_get_slot_ctx(xhci, vdev->out_ctx); + dev_dbg(xhci->dev, "%s udev %p vdev %p slot %u state %u epi %u in_ctx %p out_ctx %p\n", __func__, udev, vdev, vdev->slot_id, - GET_SLOT_STATE(le32_to_cpu(vdev->out_ctx->slot.dev_state)), epi, - vdev->in_ctx, vdev->out_ctx); + GET_SLOT_STATE(le32_to_cpu(out_slot->dev_state)), epi, + vdev->in_ctx->bytes, vdev->out_ctx->bytes); /* pass ownership of data buffer to device */ - dma_sync_single_for_device((unsigned long)buffer, length, - usb_pipein(pipe) ? - DMA_FROM_DEVICE : DMA_TO_DEVICE); + buffer_dma = dma_map_single(xhci->dev, buffer, length, + dma_direction); + if (dma_mapping_error(xhci->dev, buffer_dma)) + return -EFAULT; /* Normal TRB */ - memset(&trb, 0, sizeof(union xhci_trb)); - trb.event_cmd.cmd_trb = cpu_to_le64((dma_addr_t)buffer); /* FIXME: TD remainder */ - trb.event_cmd.status = TRB_LEN(length) | TRB_INTR_TARGET(0); - trb.event_cmd.flags = TRB_TYPE(TRB_NORMAL) | TRB_IOC; - if (usb_pipein(pipe)) - trb.event_cmd.flags |= TRB_ISP; + xhci_init_event_cmd_trb(&trb, + buffer_dma, + TRB_LEN(length) | TRB_INTR_TARGET(0), + flags); xhci_print_trb(xhci, &trb, "Request Normal"); xhci_virtdev_issue_transfer(vdev, epi, &trb, true); ret = xhci_wait_for_event(xhci, TRB_TRANSFER, &trb); xhci_print_trb(xhci, &trb, "Response Normal"); /* Regain ownership of data buffer from device */ - dma_sync_single_for_cpu((unsigned long)buffer, length, - usb_pipein(pipe) ? - DMA_FROM_DEVICE : DMA_TO_DEVICE); - + dma_unmap_single(xhci->dev, buffer_dma, length, + dma_direction); switch (ret) { case -COMP_SHORT_TX: udev->status = 0; @@ -1070,8 +1196,12 @@ static int xhci_submit_control(struct usb_device *udev, unsigned long pipe, struct usb_host *host = udev->host; struct xhci_hcd *xhci = to_xhci_hcd(host); struct xhci_virtual_device *vdev; + struct xhci_slot_ctx *out_slot; + dma_addr_t buffer_dma = 0; union xhci_trb trb; u16 typeReq = (req->requesttype << 8) | req->request; + u64 field[2]; + u32 flags; int ret; dev_dbg(xhci->dev, "%s req %u (%#x), type %u (%#x), value %u (%#x), index %u (%#x), length %u (%#x)\n", @@ -1091,10 +1221,12 @@ static int xhci_submit_control(struct usb_device *udev, unsigned long pipe, if (!vdev) return -ENODEV; + out_slot = xhci_get_slot_ctx(xhci, vdev->out_ctx); + dev_dbg(xhci->dev, "%s udev %p vdev %p slot %u state %u epi %u in_ctx %p out_ctx %p\n", __func__, udev, vdev, vdev->slot_id, - GET_SLOT_STATE(le32_to_cpu(vdev->out_ctx->slot.dev_state)), 0, - vdev->in_ctx, vdev->out_ctx); + GET_SLOT_STATE(le32_to_cpu(out_slot->dev_state)), 0, + vdev->in_ctx->bytes, vdev->out_ctx->bytes); if (req->request == USB_REQ_SET_ADDRESS) return xhci_virtdev_set_address(vdev); @@ -1104,53 +1236,59 @@ static int xhci_submit_control(struct usb_device *udev, unsigned long pipe, return ret; } - /* Pass ownership of data buffer to device */ - dma_sync_single_for_device((unsigned long)buffer, length, - (req->requesttype & USB_DIR_IN) ? - DMA_FROM_DEVICE : DMA_TO_DEVICE); - + if (length > 0) { + /* Pass ownership of data buffer to device */ + buffer_dma = dma_map_single(xhci->dev, buffer, length, + (req->requesttype & USB_DIR_IN) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); + if (dma_mapping_error(xhci->dev, buffer_dma)) + return -EFAULT; + } /* Setup TRB */ - memset(&trb, 0, sizeof(union xhci_trb)); - trb.generic.field[0] = le16_to_cpu(req->value) << 16 | + field[0] = le16_to_cpu(req->value) << 16 | req->request << 8 | req->requesttype; - trb.generic.field[1] = le16_to_cpu(req->length) << 16 | + field[1] = le16_to_cpu(req->length) << 16 | le16_to_cpu(req->index); - trb.event_cmd.status = TRB_LEN(8) | TRB_INTR_TARGET(0); - trb.event_cmd.flags = TRB_TYPE(TRB_SETUP) | TRB_IDT; - if (xhci->hci_version == 0x100 && length > 0) { + flags = TRB_TYPE(TRB_SETUP) | TRB_IDT; + if (xhci->hci_version >= 0x100 && length > 0) { if (req->requesttype & USB_DIR_IN) - trb.event_cmd.flags |= TRB_TX_TYPE(TRB_DATA_IN); + flags |= TRB_TX_TYPE(TRB_DATA_IN); else - trb.event_cmd.flags |= TRB_TX_TYPE(TRB_DATA_OUT); + flags |= TRB_TX_TYPE(TRB_DATA_OUT); } + xhci_init_event_cmd_trb(&trb, + field[1] << 32 | field[0], + TRB_LEN(8) | TRB_INTR_TARGET(0), + flags); xhci_print_trb(xhci, &trb, "Request Setup "); xhci_virtdev_issue_transfer(vdev, 0, &trb, false); /* Data TRB */ if (length > 0) { - memset(&trb, 0, sizeof(union xhci_trb)); - trb.event_cmd.cmd_trb = cpu_to_le64((dma_addr_t)buffer); /* FIXME: TD remainder */ - trb.event_cmd.status = TRB_LEN(length) | TRB_INTR_TARGET(0); - trb.event_cmd.flags = TRB_TYPE(TRB_DATA) | TRB_IOC; + flags = TRB_TYPE(TRB_DATA) | TRB_IOC; if (req->requesttype & USB_DIR_IN) - trb.event_cmd.flags |= TRB_ISP | TRB_DIR_IN; + flags |= TRB_ISP | TRB_DIR_IN; + xhci_init_event_cmd_trb(&trb, + buffer_dma, + TRB_LEN(length) | TRB_INTR_TARGET(0), + flags); xhci_print_trb(xhci, &trb, "Request Data "); xhci_virtdev_issue_transfer(vdev, 0, &trb, false); } /* Status TRB */ - memset(&trb, 0, sizeof(union xhci_trb)); - trb.event_cmd.status = TRB_INTR_TARGET(0); - if (length > 0 && req->requesttype & USB_DIR_IN) - trb.event_cmd.flags = 0; - else - trb.event_cmd.flags = TRB_DIR_IN; - trb.event_cmd.flags |= TRB_TYPE(TRB_STATUS) | TRB_IOC; + flags = TRB_TYPE(TRB_STATUS) | TRB_IOC; + if (!(length > 0 && req->requesttype & USB_DIR_IN)) + flags |= TRB_DIR_IN; + xhci_init_event_cmd_trb(&trb, + 0, + TRB_INTR_TARGET(0), + flags); xhci_print_trb(xhci, &trb, "Request Status"); xhci_virtdev_issue_transfer(vdev, 0, &trb, true); - if (length > 0 && req->requesttype & USB_DIR_IN) { + if (length > 0) { ret = xhci_wait_for_event(xhci, TRB_TRANSFER, &trb); xhci_print_trb(xhci, &trb, "Response Data "); if (ret == -COMP_SHORT_TX) @@ -1163,10 +1301,13 @@ static int xhci_submit_control(struct usb_device *udev, unsigned long pipe, xhci_print_trb(xhci, &trb, "Response Status"); dma_regain: - /* Regain ownership of data buffer from device */ - dma_sync_single_for_cpu((unsigned long)buffer, length, - (req->requesttype & USB_DIR_IN) ? - DMA_FROM_DEVICE : DMA_TO_DEVICE); + if (length > 0) { + /* Regain ownership of data buffer from device */ + dma_unmap_single(xhci->dev, buffer_dma, length, + (req->requesttype & USB_DIR_IN) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); + } + if (ret < 0) return ret; @@ -1224,8 +1365,7 @@ static void xhci_dma_alloc(struct xhci_hcd *xhci) num_ep = max(MAX_EP_RINGS, MIN_EP_RINGS + num_ep); xhci->dma_size += num_ep * sz_ep; - p = xhci->dma = dma_alloc_coherent(xhci->dma_size, DMA_ADDRESS_BROKEN); - memset(xhci->dma, 0, xhci->dma_size); + p = dma_alloc_coherent(xhci->dma_size, DMA_ADDRESS_BROKEN); xhci->sp = p; p += sz_sp; xhci->dcbaa = p; p += sz_dca; @@ -1261,7 +1401,7 @@ static void xhci_dma_alloc(struct xhci_hcd *xhci) static int xhci_halt(struct xhci_hcd *xhci) { u32 reg = readl(&xhci->op_regs->status); - u32 mask = ~XHCI_IRQS; + u32 mask = (u32)~XHCI_IRQS; if (!(reg & STS_HALT)) mask &= ~CMD_RUN; @@ -1291,7 +1431,7 @@ static int xhci_reset(struct xhci_hcd *xhci) return ret; } - return 0; + return 0; } static int xhci_start(struct xhci_hcd *xhci) @@ -1305,8 +1445,8 @@ static int xhci_start(struct xhci_hcd *xhci) ret = xhci_handshake(&xhci->op_regs->status, STS_HALT, 0, XHCI_MAX_HALT_USEC); - if (ret) { - dev_err(xhci->dev, "failed to start\n"); + if (ret) { + dev_err(xhci->dev, "failed to start\n"); return ret; } @@ -1314,7 +1454,7 @@ static int xhci_start(struct xhci_hcd *xhci) for (i = 0; i < xhci->num_usb_ports; i++) xhci_hub_port_power(xhci, i, false); - return 0; + return 0; } static int xhci_init(struct usb_host *host) @@ -1472,6 +1612,7 @@ int xhci_register(struct device_d *dev, struct xhci_data *data) xhci = xzalloc(sizeof(*xhci)); host = &xhci->host; INIT_LIST_HEAD(&xhci->vdev_list); + INIT_LIST_HEAD(&xhci->rings_list); xhci->dev = dev; xhci->cap_regs = data->regs; xhci->op_regs = (void __iomem *)xhci->cap_regs + diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index a140b1dd07..7a9315a0b6 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -37,9 +37,4 @@ static struct pci_driver xhci_pci_driver = { .id_table = xhci_pci_tbl, .probe = xhci_pci_probe, }; - -static int xhci_pci_init(void) -{ - return pci_register_driver(&xhci_pci_driver); -} -device_initcall(xhci_pci_init); +device_pci_driver(xhci_pci_driver); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 078f881183..84a14dd1fc 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -24,6 +24,8 @@ #ifndef __XHCI_H #define __XHCI_H +#include <io-64-nonatomic-lo-hi.h> + #define NUM_COMMAND_TRBS 8 #define NUM_TRANSFER_TRBS 8 #define NUM_EVENT_SEGM 1 /* only one supported */ @@ -504,6 +506,27 @@ struct xhci_doorbell_array { #define DB_VALUE_HOST 0x00000000 /** + * struct xhci_container_ctx + * @type: Type of context. Used to calculated offsets to contained contexts. + * @size: Size of the context data + * @bytes: The raw context data given to HW + * @dma: dma address of the bytes + * + * Represents either a Device or Input context. Holds a pointer to the raw + * memory used for the context (bytes) and dma address of it (dma). + */ +struct xhci_container_ctx { + unsigned type; +#define XHCI_CTX_TYPE_DEVICE 0x1 +#define XHCI_CTX_TYPE_INPUT 0x2 + + int size; + + u8 *bytes; + dma_addr_t dma; +}; + +/** * struct xhci_slot_ctx * @dev_info: Route string, device speed, hub info, and last valid endpoint * @dev_info2: Max exit latency for device number, root hub port number @@ -1144,19 +1167,11 @@ struct xhci_erst_entry { */ static inline u64 xhci_read_64(__le64 __iomem *regs) { - __u32 __iomem *ptr = (__u32 __iomem *)regs; - u64 val_lo = readl(ptr); - u64 val_hi = readl(ptr + 1); - return val_lo + (val_hi << 32); + return lo_hi_readq(regs); } static inline void xhci_write_64(const u64 val, __le64 __iomem *regs) { - __u32 __iomem *ptr = (__u32 __iomem *)regs; - u32 val_lo = lower_32_bits(val); - u32 val_hi = upper_32_bits(val); - - writel(val_lo, ptr); - writel(val_hi, ptr + 1); + lo_hi_writeq(val, regs); } /* @@ -1183,17 +1198,6 @@ struct xhci_ring { int cycle_state; }; -struct xhci_device_context { - struct xhci_slot_ctx slot; - struct xhci_ep_ctx ep[31]; -}; - -struct xhci_input_context { - struct xhci_input_control_ctx icc; - struct xhci_slot_ctx slot; - struct xhci_ep_ctx ep[31]; -}; - struct xhci_virtual_device { struct list_head list; struct usb_device *udev; @@ -1201,8 +1205,8 @@ struct xhci_virtual_device { size_t dma_size; int slot_id; struct xhci_ring *ep[USB_MAXENDPOINTS]; - struct xhci_input_context *in_ctx; - struct xhci_device_context *out_ctx; + struct xhci_container_ctx *in_ctx; + struct xhci_container_ctx *out_ctx; }; struct usb_root_hub_info { @@ -1232,7 +1236,6 @@ struct xhci_hcd { int num_sp; int page_size; int page_shift; - void *dma; size_t dma_size; __le64 *dcbaa; void *sp; diff --git a/drivers/usb/imx/chipidea-imx.c b/drivers/usb/imx/chipidea-imx.c index 8792217706..6c60c383f0 100644 --- a/drivers/usb/imx/chipidea-imx.c +++ b/drivers/usb/imx/chipidea-imx.c @@ -201,14 +201,14 @@ static int ci_register_role(struct imx_chipidea *ci) return ret; ehci = ehci_register(ci->dev, &ci->data); - if (IS_ERR(ehci)) + if (IS_ERR(ehci)) { + regulator_disable(ci->vbus); return PTR_ERR(ehci); + } ci->ehci = ehci; ci->dev->detect = ci_ehci_detect; - - regulator_disable(ci->vbus); } else { dev_err(ci->dev, "Host support not available\n"); return -ENODEV; diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig new file mode 100644 index 0000000000..7d6c9da594 --- /dev/null +++ b/drivers/usb/misc/Kconfig @@ -0,0 +1,14 @@ +# +# USB Miscellaneous driver configuration +# +comment "USB Miscellaneous drivers" + +config USB_HUB_USB251XB + bool "USB251XB Hub Controller Configuration Driver" + depends on I2C + select NLS + help + This option enables support for configuration via SMBus of the + Microchip USB251x/xBi USB 2.0 Hub Controller series. Configuration + parameters may be set in devicetree or platform data. + Say Y or M here if you need to configure such a device via SMBus. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile new file mode 100644 index 0000000000..fb69c454bd --- /dev/null +++ b/drivers/usb/misc/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the rest of the USB drivers +# (the ones that don't fit into any other categories) +# +obj-$(CONFIG_USB_HUB_USB251XB) += usb251xb.o diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c new file mode 100644 index 0000000000..97f55efa82 --- /dev/null +++ b/drivers/usb/misc/usb251xb.c @@ -0,0 +1,683 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Microchip USB251xB USB 2.0 Hi-Speed Hub Controller + * Configuration via SMBus. + * + * Copyright (c) 2017 SKIDATA AG + * + * This work is based on the USB3503 driver by Dongjin Kim and + * a not-accepted patch by Fabien Lahoudere, see: + * https://patchwork.kernel.org/patch/9257715/ + */ + +#include <common.h> +#include <init.h> +#include <driver.h> +#include <i2c/i2c.h> +#include <of.h> +#include <gpio.h> +#include <of_gpio.h> +#include <of_device.h> + +#include <linux/nls.h> + +/* Internal Register Set Addresses & Default Values acc. to DS00001692C */ +#define USB251XB_ADDR_VENDOR_ID_LSB 0x00 +#define USB251XB_ADDR_VENDOR_ID_MSB 0x01 +#define USB251XB_DEF_VENDOR_ID 0x0424 + +#define USB251XB_ADDR_PRODUCT_ID_LSB 0x02 +#define USB251XB_ADDR_PRODUCT_ID_MSB 0x03 +#define USB251XB_DEF_PRODUCT_ID_12 0x2512 /* USB2512B/12Bi */ +#define USB251XB_DEF_PRODUCT_ID_13 0x2513 /* USB2513B/13Bi */ +#define USB251XB_DEF_PRODUCT_ID_14 0x2514 /* USB2514B/14Bi */ +#define USB251XB_DEF_PRODUCT_ID_17 0x2517 /* USB2517/17i */ + +#define USB251XB_ADDR_DEVICE_ID_LSB 0x04 +#define USB251XB_ADDR_DEVICE_ID_MSB 0x05 +#define USB251XB_DEF_DEVICE_ID 0x0BB3 + +#define USB251XB_ADDR_CONFIG_DATA_1 0x06 +#define USB251XB_DEF_CONFIG_DATA_1 0x9B +#define USB251XB_ADDR_CONFIG_DATA_2 0x07 +#define USB251XB_DEF_CONFIG_DATA_2 0x20 +#define USB251XB_ADDR_CONFIG_DATA_3 0x08 +#define USB251XB_DEF_CONFIG_DATA_3 0x02 + +#define USB251XB_ADDR_NON_REMOVABLE_DEVICES 0x09 +#define USB251XB_DEF_NON_REMOVABLE_DEVICES 0x00 + +#define USB251XB_ADDR_PORT_DISABLE_SELF 0x0A +#define USB251XB_DEF_PORT_DISABLE_SELF 0x00 +#define USB251XB_ADDR_PORT_DISABLE_BUS 0x0B +#define USB251XB_DEF_PORT_DISABLE_BUS 0x00 + +#define USB251XB_ADDR_MAX_POWER_SELF 0x0C +#define USB251XB_DEF_MAX_POWER_SELF 0x01 +#define USB251XB_ADDR_MAX_POWER_BUS 0x0D +#define USB251XB_DEF_MAX_POWER_BUS 0x32 + +#define USB251XB_ADDR_MAX_CURRENT_SELF 0x0E +#define USB251XB_DEF_MAX_CURRENT_SELF 0x01 +#define USB251XB_ADDR_MAX_CURRENT_BUS 0x0F +#define USB251XB_DEF_MAX_CURRENT_BUS 0x32 + +#define USB251XB_ADDR_POWER_ON_TIME 0x10 +#define USB251XB_DEF_POWER_ON_TIME 0x32 + +#define USB251XB_ADDR_LANGUAGE_ID_HIGH 0x11 +#define USB251XB_ADDR_LANGUAGE_ID_LOW 0x12 +#define USB251XB_DEF_LANGUAGE_ID 0x0000 + +#define USB251XB_STRING_BUFSIZE 62 +#define USB251XB_ADDR_MANUFACTURER_STRING_LEN 0x13 +#define USB251XB_ADDR_MANUFACTURER_STRING 0x16 +#define USB251XB_DEF_MANUFACTURER_STRING "Microchip" + +#define USB251XB_ADDR_PRODUCT_STRING_LEN 0x14 +#define USB251XB_ADDR_PRODUCT_STRING 0x54 +#define USB251XB_DEF_PRODUCT_STRING "USB251xB/xBi/7i" + +#define USB251XB_ADDR_SERIAL_STRING_LEN 0x15 +#define USB251XB_ADDR_SERIAL_STRING 0x92 +#define USB251XB_DEF_SERIAL_STRING "" + +#define USB251XB_ADDR_BATTERY_CHARGING_ENABLE 0xD0 +#define USB251XB_DEF_BATTERY_CHARGING_ENABLE 0x00 + +#define USB251XB_ADDR_BOOST_UP 0xF6 +#define USB251XB_DEF_BOOST_UP 0x00 +#define USB251XB_ADDR_BOOST_57 0xF7 +#define USB251XB_DEF_BOOST_57 0x00 +#define USB251XB_ADDR_BOOST_14 0xF8 +#define USB251XB_DEF_BOOST_14 0x00 + +#define USB251XB_ADDR_PORT_SWAP 0xFA +#define USB251XB_DEF_PORT_SWAP 0x00 + +#define USB251XB_ADDR_PORT_MAP_12 0xFB +#define USB251XB_DEF_PORT_MAP_12 0x00 +#define USB251XB_ADDR_PORT_MAP_34 0xFC +#define USB251XB_DEF_PORT_MAP_34 0x00 /* USB251{3B/i,4B/i,7/i} only */ +#define USB251XB_ADDR_PORT_MAP_56 0xFD +#define USB251XB_DEF_PORT_MAP_56 0x00 /* USB2517/i only */ +#define USB251XB_ADDR_PORT_MAP_7 0xFE +#define USB251XB_DEF_PORT_MAP_7 0x00 /* USB2517/i only */ + +#define USB251XB_ADDR_STATUS_COMMAND 0xFF +#define USB251XB_STATUS_COMMAND_SMBUS_DOWN 0x04 +#define USB251XB_STATUS_COMMAND_RESET 0x02 +#define USB251XB_STATUS_COMMAND_ATTACH 0x01 + +#define USB251XB_I2C_REG_SZ 0x100 +#define USB251XB_I2C_WRITE_SZ 0x10 + +#define DRIVER_NAME "usb251xb" +#define DRIVER_DESC "Microchip USB 2.0 Hi-Speed Hub Controller" + +struct usb251xb { + struct device_d *dev; + struct i2c_client *i2c; + u8 skip_config; + int gpio_reset; + u16 vendor_id; + u16 product_id; + u16 device_id; + u8 conf_data1; + u8 conf_data2; + u8 conf_data3; + u8 non_rem_dev; + u8 port_disable_sp; + u8 port_disable_bp; + u8 max_power_sp; + u8 max_power_bp; + u8 max_current_sp; + u8 max_current_bp; + u8 power_on_time; + u16 lang_id; + u8 manufacturer_len; + u8 product_len; + u8 serial_len; + char manufacturer[USB251XB_STRING_BUFSIZE]; + char product[USB251XB_STRING_BUFSIZE]; + char serial[USB251XB_STRING_BUFSIZE]; + u8 bat_charge_en; + u8 boost_up; + u8 boost_57; + u8 boost_14; + u8 port_swap; + u8 port_map12; + u8 port_map34; + u8 port_map56; + u8 port_map7; + u8 status; +}; + +struct usb251xb_data { + u16 product_id; + u8 port_cnt; + bool led_support; + bool bat_support; + char product_str[USB251XB_STRING_BUFSIZE / 2]; /* ASCII string */ +}; + +static const struct usb251xb_data usb2512b_data = { + .product_id = 0x2512, + .port_cnt = 2, + .led_support = false, + .bat_support = true, + .product_str = "USB2512B", +}; + +static const struct usb251xb_data usb2512bi_data = { + .product_id = 0x2512, + .port_cnt = 2, + .led_support = false, + .bat_support = true, + .product_str = "USB2512Bi", +}; + +static const struct usb251xb_data usb2513b_data = { + .product_id = 0x2513, + .port_cnt = 3, + .led_support = false, + .bat_support = true, + .product_str = "USB2513B", +}; + +static const struct usb251xb_data usb2513bi_data = { + .product_id = 0x2513, + .port_cnt = 3, + .led_support = false, + .bat_support = true, + .product_str = "USB2513Bi", +}; + +static const struct usb251xb_data usb2514b_data = { + .product_id = 0x2514, + .port_cnt = 4, + .led_support = false, + .bat_support = true, + .product_str = "USB2514B", +}; + +static const struct usb251xb_data usb2514bi_data = { + .product_id = 0x2514, + .port_cnt = 4, + .led_support = false, + .bat_support = true, + .product_str = "USB2514Bi", +}; + +static const struct usb251xb_data usb2517_data = { + .product_id = 0x2517, + .port_cnt = 7, + .led_support = true, + .bat_support = false, + .product_str = "USB2517", +}; + +static const struct usb251xb_data usb2517i_data = { + .product_id = 0x2517, + .port_cnt = 7, + .led_support = true, + .bat_support = false, + .product_str = "USB2517i", +}; + +static void usb251xb_reset(struct usb251xb *hub, int state) +{ + if (!gpio_is_valid(hub->gpio_reset)) + return; + + gpio_set_active(hub->gpio_reset, state); + + /* wait for hub recovery/stabilization */ + if (!state) + udelay(750); /* >=500us at power on */ + else + udelay(10); /* >=1us at power down */ +} + +static int usb251xb_connect(struct usb251xb *hub) +{ + struct device_d *dev = hub->dev; + int err, i; + char i2c_wb[USB251XB_I2C_REG_SZ]; + + memset(i2c_wb, 0, USB251XB_I2C_REG_SZ); + + if (hub->skip_config) { + dev_info(dev, "Skip hub configuration, only attach.\n"); + i2c_wb[0] = 0x01; + i2c_wb[1] = USB251XB_STATUS_COMMAND_ATTACH; + + usb251xb_reset(hub, 0); + + err = i2c_smbus_write_i2c_block_data(hub->i2c, + USB251XB_ADDR_STATUS_COMMAND, 2, i2c_wb); + if (err) { + dev_err(dev, "attaching hub failed: %d\n", err); + return err; + } + return 0; + } + + i2c_wb[USB251XB_ADDR_VENDOR_ID_MSB] = (hub->vendor_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_VENDOR_ID_LSB] = hub->vendor_id & 0xFF; + i2c_wb[USB251XB_ADDR_PRODUCT_ID_MSB] = (hub->product_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_PRODUCT_ID_LSB] = hub->product_id & 0xFF; + i2c_wb[USB251XB_ADDR_DEVICE_ID_MSB] = (hub->device_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_DEVICE_ID_LSB] = hub->device_id & 0xFF; + i2c_wb[USB251XB_ADDR_CONFIG_DATA_1] = hub->conf_data1; + i2c_wb[USB251XB_ADDR_CONFIG_DATA_2] = hub->conf_data2; + i2c_wb[USB251XB_ADDR_CONFIG_DATA_3] = hub->conf_data3; + i2c_wb[USB251XB_ADDR_NON_REMOVABLE_DEVICES] = hub->non_rem_dev; + i2c_wb[USB251XB_ADDR_PORT_DISABLE_SELF] = hub->port_disable_sp; + i2c_wb[USB251XB_ADDR_PORT_DISABLE_BUS] = hub->port_disable_bp; + i2c_wb[USB251XB_ADDR_MAX_POWER_SELF] = hub->max_power_sp; + i2c_wb[USB251XB_ADDR_MAX_POWER_BUS] = hub->max_power_bp; + i2c_wb[USB251XB_ADDR_MAX_CURRENT_SELF] = hub->max_current_sp; + i2c_wb[USB251XB_ADDR_MAX_CURRENT_BUS] = hub->max_current_bp; + i2c_wb[USB251XB_ADDR_POWER_ON_TIME] = hub->power_on_time; + i2c_wb[USB251XB_ADDR_LANGUAGE_ID_HIGH] = (hub->lang_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_LANGUAGE_ID_LOW] = hub->lang_id & 0xFF; + i2c_wb[USB251XB_ADDR_MANUFACTURER_STRING_LEN] = hub->manufacturer_len; + i2c_wb[USB251XB_ADDR_PRODUCT_STRING_LEN] = hub->product_len; + i2c_wb[USB251XB_ADDR_SERIAL_STRING_LEN] = hub->serial_len; + memcpy(&i2c_wb[USB251XB_ADDR_MANUFACTURER_STRING], hub->manufacturer, + USB251XB_STRING_BUFSIZE); + memcpy(&i2c_wb[USB251XB_ADDR_SERIAL_STRING], hub->serial, + USB251XB_STRING_BUFSIZE); + memcpy(&i2c_wb[USB251XB_ADDR_PRODUCT_STRING], hub->product, + USB251XB_STRING_BUFSIZE); + i2c_wb[USB251XB_ADDR_BATTERY_CHARGING_ENABLE] = hub->bat_charge_en; + i2c_wb[USB251XB_ADDR_BOOST_UP] = hub->boost_up; + i2c_wb[USB251XB_ADDR_BOOST_57] = hub->boost_57; + i2c_wb[USB251XB_ADDR_BOOST_14] = hub->boost_14; + i2c_wb[USB251XB_ADDR_PORT_SWAP] = hub->port_swap; + i2c_wb[USB251XB_ADDR_PORT_MAP_12] = hub->port_map12; + i2c_wb[USB251XB_ADDR_PORT_MAP_34] = hub->port_map34; + i2c_wb[USB251XB_ADDR_PORT_MAP_56] = hub->port_map56; + i2c_wb[USB251XB_ADDR_PORT_MAP_7] = hub->port_map7; + i2c_wb[USB251XB_ADDR_STATUS_COMMAND] = USB251XB_STATUS_COMMAND_ATTACH; + + usb251xb_reset(hub, 0); + + /* write registers */ + for (i = 0; i < (USB251XB_I2C_REG_SZ / USB251XB_I2C_WRITE_SZ); i++) { + int offset = i * USB251XB_I2C_WRITE_SZ; + char wbuf[USB251XB_I2C_WRITE_SZ + 1]; + + /* The first data byte transferred tells the hub how many data + * bytes will follow (byte count). + */ + wbuf[0] = USB251XB_I2C_WRITE_SZ; + memcpy(&wbuf[1], &i2c_wb[offset], USB251XB_I2C_WRITE_SZ); + + dev_dbg(dev, "writing %d byte block %d to 0x%02X\n", + USB251XB_I2C_WRITE_SZ, i, offset); + + err = i2c_smbus_write_i2c_block_data(hub->i2c, offset, + USB251XB_I2C_WRITE_SZ + 1, + wbuf); + if (err) + goto out_err; + } + + dev_info(dev, "Hub configuration was successful.\n"); + return 0; + +out_err: + dev_err(dev, "configuring block %d failed: %d\n", i, err); + return err; +} + +#ifdef CONFIG_OFDEVICE +static int usb251xb_get_ofdata(struct usb251xb *hub, + struct usb251xb_data *data) +{ + struct device_d *dev = hub->dev; + struct device_node *np = dev->device_node; + int len, err, i; + u32 port, property_u32 = 0; + const u32 *cproperty_u32; + const char *cproperty_char; + enum of_gpio_flags of_flags; + unsigned long flags = GPIOF_OUT_INIT_ACTIVE; + char str[USB251XB_STRING_BUFSIZE / 2]; + struct property *prop; + const __be32 *p; + + if (!np) { + dev_err(dev, "failed to get ofdata\n"); + return -ENODEV; + } + + if (of_get_property(np, "skip-config", NULL)) + hub->skip_config = 1; + else + hub->skip_config = 0; + + hub->gpio_reset = of_get_named_gpio_flags(np, "reset-gpios", 0, + &of_flags); + if (gpio_is_valid(hub->gpio_reset)) { + char *name; + int ret; + + if (of_flags & OF_GPIO_ACTIVE_LOW) + flags |= GPIOF_ACTIVE_LOW; + + name = basprintf("%s reset", dev_name(dev)); + ret = gpio_request_one(hub->gpio_reset, flags, name); + if (ret < 0) + return ret; + } else if (hub->gpio_reset == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else { + err = hub->gpio_reset; + dev_err(dev, "unable to request GPIO reset pin (%d)\n", err); + return err; + } + + if (of_property_read_u16_array(np, "vendor-id", &hub->vendor_id, 1)) + hub->vendor_id = USB251XB_DEF_VENDOR_ID; + + if (of_property_read_u16_array(np, "product-id", + &hub->product_id, 1)) + hub->product_id = data->product_id; + + if (of_property_read_u16_array(np, "device-id", &hub->device_id, 1)) + hub->device_id = USB251XB_DEF_DEVICE_ID; + + hub->conf_data1 = USB251XB_DEF_CONFIG_DATA_1; + if (of_get_property(np, "self-powered", NULL)) { + hub->conf_data1 |= BIT(7); + + /* Configure Over-Current sens when self-powered */ + hub->conf_data1 &= ~BIT(2); + if (of_get_property(np, "ganged-sensing", NULL)) + hub->conf_data1 &= ~BIT(1); + else if (of_get_property(np, "individual-sensing", NULL)) + hub->conf_data1 |= BIT(1); + } else if (of_get_property(np, "bus-powered", NULL)) { + hub->conf_data1 &= ~BIT(7); + + /* Disable Over-Current sense when bus-powered */ + hub->conf_data1 |= BIT(2); + } + + if (of_get_property(np, "disable-hi-speed", NULL)) + hub->conf_data1 |= BIT(5); + + if (of_get_property(np, "multi-tt", NULL)) + hub->conf_data1 |= BIT(4); + else if (of_get_property(np, "single-tt", NULL)) + hub->conf_data1 &= ~BIT(4); + + if (of_get_property(np, "disable-eop", NULL)) + hub->conf_data1 |= BIT(3); + + if (of_get_property(np, "individual-port-switching", NULL)) + hub->conf_data1 |= BIT(0); + else if (of_get_property(np, "ganged-port-switching", NULL)) + hub->conf_data1 &= ~BIT(0); + + hub->conf_data2 = USB251XB_DEF_CONFIG_DATA_2; + if (of_get_property(np, "dynamic-power-switching", NULL)) + hub->conf_data2 |= BIT(7); + + if (!of_property_read_u32(np, "oc-delay-us", &property_u32)) { + if (property_u32 == 100) { + /* 100 us*/ + hub->conf_data2 &= ~BIT(5); + hub->conf_data2 &= ~BIT(4); + } else if (property_u32 == 4000) { + /* 4 ms */ + hub->conf_data2 &= ~BIT(5); + hub->conf_data2 |= BIT(4); + } else if (property_u32 == 16000) { + /* 16 ms */ + hub->conf_data2 |= BIT(5); + hub->conf_data2 |= BIT(4); + } else { + /* 8 ms (DEFAULT) */ + hub->conf_data2 |= BIT(5); + hub->conf_data2 &= ~BIT(4); + } + } + + if (of_get_property(np, "compound-device", NULL)) + hub->conf_data2 |= BIT(3); + + hub->conf_data3 = USB251XB_DEF_CONFIG_DATA_3; + if (of_get_property(np, "port-mapping-mode", NULL)) + hub->conf_data3 |= BIT(3); + + if (data->led_support && of_get_property(np, "led-usb-mode", NULL)) + hub->conf_data3 &= ~BIT(1); + + if (of_get_property(np, "string-support", NULL)) + hub->conf_data3 |= BIT(0); + + hub->non_rem_dev = USB251XB_DEF_NON_REMOVABLE_DEVICES; + cproperty_u32 = of_get_property(np, "non-removable-ports", &len); + if (cproperty_u32 && (len / sizeof(u32)) > 0) { + for (i = 0; i < len / sizeof(u32); i++) { + u32 port = be32_to_cpu(cproperty_u32[i]); + + if ((port >= 1) && (port <= data->port_cnt)) + hub->non_rem_dev |= BIT(port); + else + dev_warn(dev, "NRD port %u doesn't exist\n", + port); + } + } + + hub->port_disable_sp = USB251XB_DEF_PORT_DISABLE_SELF; + cproperty_u32 = of_get_property(np, "sp-disabled-ports", &len); + if (cproperty_u32 && (len / sizeof(u32)) > 0) { + for (i = 0; i < len / sizeof(u32); i++) { + u32 port = be32_to_cpu(cproperty_u32[i]); + + if ((port >= 1) && (port <= data->port_cnt)) + hub->port_disable_sp |= BIT(port); + else + dev_warn(dev, "PDS port %u doesn't exist\n", + port); + } + } + + hub->port_disable_bp = USB251XB_DEF_PORT_DISABLE_BUS; + cproperty_u32 = of_get_property(np, "bp-disabled-ports", &len); + if (cproperty_u32 && (len / sizeof(u32)) > 0) { + for (i = 0; i < len / sizeof(u32); i++) { + u32 port = be32_to_cpu(cproperty_u32[i]); + + if ((port >= 1) && (port <= data->port_cnt)) + hub->port_disable_bp |= BIT(port); + else + dev_warn(dev, "PDB port %u doesn't exist\n", + port); + } + } + + hub->max_power_sp = USB251XB_DEF_MAX_POWER_SELF; + if (!of_property_read_u32(np, "sp-max-total-current-microamp", + &property_u32)) + hub->max_power_sp = min_t(u8, property_u32 / 2000, 50); + + hub->max_power_bp = USB251XB_DEF_MAX_POWER_BUS; + if (!of_property_read_u32(np, "bp-max-total-current-microamp", + &property_u32)) + hub->max_power_bp = min_t(u8, property_u32 / 2000, 255); + + hub->max_current_sp = USB251XB_DEF_MAX_CURRENT_SELF; + if (!of_property_read_u32(np, "sp-max-removable-current-microamp", + &property_u32)) + hub->max_current_sp = min_t(u8, property_u32 / 2000, 50); + + hub->max_current_bp = USB251XB_DEF_MAX_CURRENT_BUS; + if (!of_property_read_u32(np, "bp-max-removable-current-microamp", + &property_u32)) + hub->max_current_bp = min_t(u8, property_u32 / 2000, 255); + + hub->power_on_time = USB251XB_DEF_POWER_ON_TIME; + if (!of_property_read_u32(np, "power-on-time-ms", &property_u32)) + hub->power_on_time = min_t(u8, property_u32 / 2, 255); + + if (of_property_read_u16_array(np, "language-id", &hub->lang_id, 1)) + hub->lang_id = USB251XB_DEF_LANGUAGE_ID; + + cproperty_char = of_get_property(np, "manufacturer", NULL); + strlcpy(str, cproperty_char ? : USB251XB_DEF_MANUFACTURER_STRING, + sizeof(str)); + hub->manufacturer_len = strlen(str) & 0xFF; + memset(hub->manufacturer, 0, USB251XB_STRING_BUFSIZE); + len = min_t(size_t, USB251XB_STRING_BUFSIZE / 2, strlen(str)); + len = utf8s_to_utf16s(str, len, UTF16_LITTLE_ENDIAN, + (wchar_t *)hub->manufacturer, + USB251XB_STRING_BUFSIZE); + + cproperty_char = of_get_property(np, "product", NULL); + strlcpy(str, cproperty_char ? : data->product_str, sizeof(str)); + hub->product_len = strlen(str) & 0xFF; + memset(hub->product, 0, USB251XB_STRING_BUFSIZE); + len = min_t(size_t, USB251XB_STRING_BUFSIZE / 2, strlen(str)); + len = utf8s_to_utf16s(str, len, UTF16_LITTLE_ENDIAN, + (wchar_t *)hub->product, + USB251XB_STRING_BUFSIZE); + + cproperty_char = of_get_property(np, "serial", NULL); + strlcpy(str, cproperty_char ? : USB251XB_DEF_SERIAL_STRING, + sizeof(str)); + hub->serial_len = strlen(str) & 0xFF; + memset(hub->serial, 0, USB251XB_STRING_BUFSIZE); + len = min_t(size_t, USB251XB_STRING_BUFSIZE / 2, strlen(str)); + len = utf8s_to_utf16s(str, len, UTF16_LITTLE_ENDIAN, + (wchar_t *)hub->serial, + USB251XB_STRING_BUFSIZE); + + /* + * The datasheet documents the register as 'Port Swap' but the register + * controls the USB DP/DM signal swapping + */ + hub->port_swap = USB251XB_DEF_PORT_SWAP; + of_property_for_each_u32(np, "swap-dx-lanes", prop, p, port) { + if ((port >= 0) && (port <= data->port_cnt)) + hub->port_swap |= BIT(port); + } + + /* The following parameters are currently not exposed to devicetree, but + * may be as soon as needed. + */ + hub->bat_charge_en = USB251XB_DEF_BATTERY_CHARGING_ENABLE; + hub->boost_up = USB251XB_DEF_BOOST_UP; + hub->boost_57 = USB251XB_DEF_BOOST_57; + hub->boost_14 = USB251XB_DEF_BOOST_14; + hub->port_map12 = USB251XB_DEF_PORT_MAP_12; + hub->port_map34 = USB251XB_DEF_PORT_MAP_34; + hub->port_map56 = USB251XB_DEF_PORT_MAP_56; + hub->port_map7 = USB251XB_DEF_PORT_MAP_7; + + return 0; +} + +static const struct of_device_id usb251xb_of_match[] = { + { + .compatible = "microchip,usb2512b", + .data = &usb2512b_data, + }, { + .compatible = "microchip,usb2512bi", + .data = &usb2512bi_data, + }, { + .compatible = "microchip,usb2513b", + .data = &usb2513b_data, + }, { + .compatible = "microchip,usb2513bi", + .data = &usb2513bi_data, + }, { + .compatible = "microchip,usb2514b", + .data = &usb2514b_data, + }, { + .compatible = "microchip,usb2514bi", + .data = &usb2514bi_data, + }, { + .compatible = "microchip,usb2517", + .data = &usb2517_data, + }, { + .compatible = "microchip,usb2517i", + .data = &usb2517i_data, + }, { + /* sentinel */ + } +}; +#else /* CONFIG_OF */ +static int usb251xb_get_ofdata(struct usb251xb *hub, + struct usb251xb_data *data) +{ + return 0; +} +#endif /* CONFIG_OF */ + +static int usb251xb_probe(struct usb251xb *hub) +{ + struct device_d *dev = hub->dev; + struct device_node *np = dev->device_node; + const struct of_device_id *of_id = of_match_device(usb251xb_of_match, + dev); + int err; + + if (np) { + err = usb251xb_get_ofdata(hub, + (struct usb251xb_data *)of_id->data); + if (err) { + dev_err(dev, "failed to get ofdata: %d\n", err); + return err; + } + } + + err = usb251xb_connect(hub); + if (err) { + dev_err(dev, "Failed to connect hub (%d)\n", err); + return err; + } + + dev_info(dev, "Hub probed successfully\n"); + + return 0; +} + +static int usb251xb_i2c_probe(struct device_d *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct usb251xb *hub; + + hub = xzalloc(sizeof(struct usb251xb)); + + i2c_set_clientdata(i2c, hub); + hub->dev = &i2c->dev; + hub->i2c = i2c; + + return usb251xb_probe(hub); +} + +static const struct platform_device_id usb251xb_id[] = { + { "usb2512b", 0 }, + { "usb2512bi", 0 }, + { "usb2513b", 0 }, + { "usb2513bi", 0 }, + { "usb2514b", 0 }, + { "usb2514bi", 0 }, + { "usb2517", 0 }, + { "usb2517i", 0 }, + { /* sentinel */ } +}; + +static struct driver_d usb251xb_i2c_driver = { + .name = DRIVER_NAME, + .probe = usb251xb_i2c_probe, + .id_table = usb251xb_id, + .of_compatible = DRV_OF_COMPAT(usb251xb_of_match), +}; +device_i2c_driver(usb251xb_i2c_driver); diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 27e9f6d8b4..2793ee93d9 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -10,7 +10,7 @@ menuconfig WATCHDOG if WATCHDOG -menuconfig WATCHDOG_POLLER +config WATCHDOG_POLLER bool "Watchdog periodic feeder support" select POLLER help @@ -22,6 +22,12 @@ config WATCHDOG_AR9344 help Add support for watchdog on the QCA AR9344 SoC. +config WATCHDOG_EFI + bool "Generic EFI Watchdog Driver" + depends on EFI_BOOTUP + help + Add support for the EFI watchdog. + config WATCHDOG_DAVINCI bool "TI Davinci" depends on ARCH_DAVINCI diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index faf06110a3..69189ba1f3 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_WATCHDOG) += wd_core.o obj-$(CONFIG_WATCHDOG_AR9344) += ar9344_wdt.o +obj-$(CONFIG_WATCHDOG_EFI) += efi_wdt.o obj-$(CONFIG_WATCHDOG_DAVINCI) += davinci_wdt.o obj-$(CONFIG_WATCHDOG_OMAP) += omap_wdt.o obj-$(CONFIG_WATCHDOG_MXS28) += im28wd.o diff --git a/drivers/watchdog/efi_wdt.c b/drivers/watchdog/efi_wdt.c new file mode 100644 index 0000000000..8e3e51b7a9 --- /dev/null +++ b/drivers/watchdog/efi_wdt.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2019 Oleksij Rempel <o.rempel@pengutronix.de>, Pengutronix + */ + +#include <common.h> +#include <init.h> +#include <driver.h> +#include <efi.h> +#include <efi/efi.h> +#include <watchdog.h> + +struct efi_wdt_priv { + struct watchdog wd; + struct device_d *dev; +}; + +#define to_efi_wdt(h) container_of(h, struct efi_wdt_priv, wd) + +static int efi_wdt_set_timeout(struct watchdog *wd, unsigned timeout) +{ + struct efi_wdt_priv *priv = to_efi_wdt(wd); + efi_status_t efiret; + + efiret = BS->set_watchdog_timer(timeout, 0, 0, NULL); + if (EFI_ERROR(efiret)) { + dev_err(priv->dev, "filed to set EFI watchdog: %lx\n", efiret); + return -EINVAL; + } + + return 0; +} + +static int efi_wdt_probe(struct device_d *dev) +{ + struct efi_wdt_priv *priv; + int ret; + + priv = xzalloc(sizeof(*priv)); + + priv->wd.set_timeout = efi_wdt_set_timeout; + priv->wd.hwdev = dev; + priv->dev = dev; + + dev->priv = priv; + + priv->wd.timeout_max = U32_MAX; + + ret = watchdog_register(&priv->wd); + if (ret) + goto on_error; + + return 0; + +on_error: + free(priv); + return ret; +} + +static struct driver_d efi_wdt_driver = { + .name = "efi-wdt", + .probe = efi_wdt_probe, +}; +device_platform_driver(efi_wdt_driver); |