diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 4d95269..4f936af 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -43,9 +43,9 @@ select GPIOLIB select CLKDEV_LOOKUP select HAS_DEBUG_LL - select HAVE_MACH_ARM_HEAD select HAVE_CLK select PINCTRL_AT91 + select COMMON_CLK_AT91 if COMMON_CLK_OF_PROVIDER config ARCH_BCM283X diff --git a/arch/arm/boards/at91sam9x5ek/Makefile b/arch/arm/boards/at91sam9x5ek/Makefile index 559df8f..4939b7e 100644 --- a/arch/arm/boards/at91sam9x5ek/Makefile +++ b/arch/arm/boards/at91sam9x5ek/Makefile @@ -1,3 +1,4 @@ obj-y += init.o obj-y += hw_version.o bbenv-$(CONFIG_DEFAULT_ENVIRONMENT_GENERIC) += defaultenv-at91sam9x5ek +lwl-y += lowlevel.o diff --git a/arch/arm/boards/at91sam9x5ek/hw_version.c b/arch/arm/boards/at91sam9x5ek/hw_version.c index 2f84d82..d1ca036 100644 --- a/arch/arm/boards/at91sam9x5ek/hw_version.c +++ b/arch/arm/boards/at91sam9x5ek/hw_version.c @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -250,7 +251,7 @@ return 0; } -void at91sam9x5ek_devices_detect_hw(void) +static int at91sam9x5ek_devices_detect_hw(void) { at91sam9x5ek_devices_detect_one("/dev/ds24310"); at91sam9x5ek_devices_detect_one("/dev/ds24311"); @@ -262,4 +263,7 @@ if (at91sam9x5ek_cm_is_vendor(VENDOR_COGENT)) of_register_fixup(cm_cogent_fixup, NULL); + + return 0; } +late_initcall(at91sam9x5ek_devices_detect_hw); diff --git a/arch/arm/boards/at91sam9x5ek/hw_version.h b/arch/arm/boards/at91sam9x5ek/hw_version.h index 91fd429..3f3c800 100644 --- a/arch/arm/boards/at91sam9x5ek/hw_version.h +++ b/arch/arm/boards/at91sam9x5ek/hw_version.h @@ -29,6 +29,5 @@ bool at91sam9x5ek_cm_is_vendor(enum vendor_id vid); bool at91sam9x5ek_ek_is_vendor(enum vendor_id vid); bool at91sam9x5ek_dm_is_vendor(enum vendor_id vid); -void at91sam9x5ek_devices_detect_hw(void); #endif /* __HW_REVISION_H__ */ diff --git a/arch/arm/boards/at91sam9x5ek/init.c b/arch/arm/boards/at91sam9x5ek/init.c index 9fe117c..646cff5 100644 --- a/arch/arm/boards/at91sam9x5ek/init.c +++ b/arch/arm/boards/at91sam9x5ek/init.c @@ -45,28 +45,6 @@ #include "hw_version.h" -struct w1_gpio_platform_data w1_pdata = { - .pin = AT91_PIN_PB18, - .ext_pullup_enable_pin = -EINVAL, - .is_open_drain = 0, -}; - -static struct atmel_nand_data nand_pdata = { - .ale = 21, - .cle = 22, - .det_pin = -EINVAL, - .rdy_pin = AT91_PIN_PD5, - .enable_pin = AT91_PIN_PD4, - .has_pmecc = 1, - .ecc_mode = NAND_ECC_HW, - .pmecc_sector_size = 512, - .pmecc_corr_cap = 2, -#if defined(CONFIG_MTD_NAND_ATMEL_BUSWIDTH_16) - .bus_width_16 = 1, -#endif - .on_flash_bbt = 1, -}; - static struct sam9_smc_config cm_nand_smc_config = { .ncs_read_setup = 0, .nrd_setup = 1, @@ -85,10 +63,27 @@ .tdf_cycles = 1, }; -static void ek_add_device_nand(void) +static int ek_add_device_smc(void) { + unsigned long csa; + csa = at91_sys_read(AT91_MATRIX_EBICSA); + + /* Enable CS3 */ + csa |= AT91_MATRIX_EBI_CS3A_SMC_NANDFLASH; + /* NAND flash on D16 */ + csa |= AT91_MATRIX_NFD0_ON_D16; + + /* Configure IO drive */ + csa &= ~AT91_MATRIX_EBI_EBI_IOSR_NORMAL; + at91_sys_write(AT91_MATRIX_EBICSA, csa); + + add_generic_device("at91sam9-smc", + DEVICE_ID_SINGLE, NULL, + AT91SAM9X5_BASE_SMC, 0x200, + IORESOURCE_MEM, NULL); + /* setup bus-width (8 or 16) */ - if (nand_pdata.bus_width_16) + if (IS_ENABLED(CONFIG_MTD_NAND_ATMEL_BUSWIDTH_16)) cm_nand_smc_config.mode |= AT91_SMC_DBW_16; else cm_nand_smc_config.mode |= AT91_SMC_DBW_8; @@ -97,28 +92,26 @@ sam9_smc_configure(0, 3, &cm_nand_smc_config); if (at91sam9x5ek_cm_is_vendor(VENDOR_COGENT)) { - unsigned long csa; - csa = at91_sys_read(AT91_MATRIX_EBICSA); csa |= AT91_MATRIX_EBI_VDDIOMSEL_1_8V; at91_sys_write(AT91_MATRIX_EBICSA, csa); } - at91_add_device_nand(&nand_pdata); + return 0; } +fs_initcall(ek_add_device_smc); -static struct macb_platform_data macb_pdata = { - .phy_interface = PHY_INTERFACE_MODE_RMII, - .phy_addr = 0, -}; - -static void ek_add_device_eth(void) +static int ek_register_mac_address(void) { - if (w1_local_mac_address_register(0, "tml", "w1-2d-0")) - w1_local_mac_address_register(0, "tml", "w1-23-0"); + int ret; - at91_add_device_eth(0, &macb_pdata); + ret = w1_local_mac_address_register(0, "tml", "w1-2d-0"); + if (!ret) + return ret; + + return w1_local_mac_address_register(0, "tml", "w1-23-0"); } +late_initcall(ek_register_mac_address); #if defined(CONFIG_DRIVER_VIDEO_ATMEL_HLCD) /* @@ -163,164 +156,12 @@ static void ek_add_device_lcdc(void) {} #endif -/* - * MCI (SD/MMC) - */ -/* mci0 detect_pin is revision dependent */ -static struct atmel_mci_platform_data mci0_data = { - .bus_width = 4, - .detect_pin = AT91_PIN_PD15, - .wp_pin = -EINVAL, -}; - -static void ek_add_device_mci(void) -{ - if (at91sam9x5ek_cm_is_vendor(VENDOR_COGENT)) - mci0_data.detect_pin = -EINVAL; - - /* MMC0 */ - at91_add_device_mci(0, &mci0_data); -} - -struct qt1070_platform_data qt1070_pdata = { - .irq_pin = AT91_PIN_PA7, -}; - -static struct i2c_board_info i2c_devices[] = { - { - .platform_data = &qt1070_pdata, - I2C_BOARD_INFO("qt1070", 0x1b), - }, { - I2C_BOARD_INFO("24c512", 0x51) - }, -}; - -static void ek_add_device_i2c(void) -{ - at91_set_gpio_input(qt1070_pdata.irq_pin, 0); - at91_set_deglitch(qt1070_pdata.irq_pin, 1); - at91_add_device_i2c(0, i2c_devices, ARRAY_SIZE(i2c_devices)); -} - -static const struct spi_board_info ek_cm_cogent_spi_devices[] = { - { - .name = "mtd_dataflash", - .chip_select = 0, - .max_speed_hz = 15 * 1000 * 1000, - .bus_num = 0, - } -}; - -static const struct spi_board_info ek_spi_devices[] = { - { - .name = "m25p80", - .chip_select = 0, - .max_speed_hz = 30 * 1000 * 1000, - .bus_num = 0, - } -}; - -static unsigned spi0_standard_cs[] = { AT91_PIN_PA14}; -static struct at91_spi_platform_data spi_pdata = { - .chipselect = spi0_standard_cs, - .num_chipselect = ARRAY_SIZE(spi0_standard_cs), -}; - -static void ek_add_device_spi(void) -{ - if (at91sam9x5ek_cm_is_vendor(VENDOR_COGENT)) - spi_register_board_info(ek_cm_cogent_spi_devices, - ARRAY_SIZE(ek_cm_cogent_spi_devices)); - else - spi_register_board_info(ek_spi_devices, - ARRAY_SIZE(ek_spi_devices)); - at91_add_device_spi(0, &spi_pdata); -} - -#if defined(CONFIG_USB_OHCI) || defined(CONFIG_USB_EHCI) -/* - * USB HS Host port (common to OHCI & EHCI) - */ -static struct at91_usbh_data ek_usbh_hs_data = { - .ports = 2, - .vbus_pin = {AT91_PIN_PD19, AT91_PIN_PD20}, -}; - -static void ek_add_device_usb(void) -{ - at91_add_device_usbh_ohci(&ek_usbh_hs_data); - at91_add_device_usbh_ehci(&ek_usbh_hs_data); -} -#else -static void ek_add_device_usb(void) {} -#endif - -struct gpio_led leds[] = { - { - .gpio = AT91_PIN_PB18, - .active_low = 1, - .led = { - .name = "d1", - }, - }, { - .gpio = AT91_PIN_PD21, - .led = { - .name = "d2", - }, - }, -}; - -static void __init ek_add_led(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(leds); i++) { - at91_set_gpio_output(leds[i].gpio, leds[i].active_low); - led_gpio_register(&leds[i]); - } - led_set_trigger(LED_TRIGGER_HEARTBEAT, &leds[1].led); -} - -static int at91sam9x5ek_mem_init(void) -{ - at91_add_device_sdram(0); - - return 0; -} -mem_initcall(at91sam9x5ek_mem_init); - -static void ek_add_device_w1(void) -{ - at91_set_gpio_input(w1_pdata.pin, 0); - at91_set_multi_drive(w1_pdata.pin, 1); - add_generic_device_res("w1-gpio", DEVICE_ID_SINGLE, NULL, 0, &w1_pdata); - - at91sam9x5ek_devices_detect_hw(); -} - static int at91sam9x5ek_devices_init(void) { - ek_add_device_w1(); - ek_add_device_nand(); - ek_add_device_eth(); - ek_add_device_spi(); - ek_add_device_mci(); - ek_add_device_usb(); - ek_add_led(); - ek_add_device_i2c(); ek_add_device_lcdc(); armlinux_set_architecture(CONFIG_MACH_AT91SAM9X5EK); - devfs_add_partition("nand0", 0x00000, SZ_256K, DEVFS_PARTITION_FIXED, "at91bootstrap_raw"); - dev_add_bb_dev("at91bootstrap_raw", "at91bootstrap"); - devfs_add_partition("nand0", SZ_256K, SZ_256K + SZ_128K, DEVFS_PARTITION_FIXED, "self_raw"); - dev_add_bb_dev("self_raw", "self0"); - devfs_add_partition("nand0", SZ_512K + SZ_128K, SZ_128K, DEVFS_PARTITION_FIXED, "env_raw"); - dev_add_bb_dev("env_raw", "env0"); - devfs_add_partition("nand0", SZ_512K + SZ_256K, SZ_128K, DEVFS_PARTITION_FIXED, "env_raw1"); - dev_add_bb_dev("env_raw1", "env1"); - if (IS_ENABLED(CONFIG_DEFAULT_ENVIRONMENT_GENERIC)) defaultenv_append_directory(defaultenv_at91sam9x5ek); @@ -333,15 +174,6 @@ barebox_set_model("Atmel at91sam9x5-ek"); barebox_set_hostname("at91sam9x5-ek"); - at91_register_uart(0, 0); - at91_register_uart(1, 0); return 0; } console_initcall(at91sam9x5ek_console_init); - -static int at91sam9x5ek_main_clock(void) -{ - at91_set_main_clock(12000000); - return 0; -} -pure_initcall(at91sam9x5ek_main_clock); diff --git a/arch/arm/boards/at91sam9x5ek/lowlevel.c b/arch/arm/boards/at91sam9x5ek/lowlevel.c new file mode 100644 index 0000000..acf80d7 --- /dev/null +++ b/arch/arm/boards/at91sam9x5ek/lowlevel.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include +#include +#include + +extern char __dtb_at91sam9x5ek_start[]; + +ENTRY_FUNCTION(start_at91sam9x5ek, r0, r1, r2) +{ + void *fdt; + + arm_cpu_lowlevel_init(); + arm_setup_stack(AT91SAM9X5_SRAM_BASE + AT91SAM9X5_SRAM_SIZE - 16); + + fdt = __dtb_at91sam9x5ek_start - get_runtime_offset(); + + barebox_arm_entry(AT91_CHIPSELECT_1, at91sam9x5_get_ddram_size(), fdt); +} diff --git a/arch/arm/configs/at91sam9x5ek_defconfig b/arch/arm/configs/at91sam9x5ek_defconfig index 6ee0052..dd42755 100644 --- a/arch/arm/configs/at91sam9x5ek_defconfig +++ b/arch/arm/configs/at91sam9x5ek_defconfig @@ -1,12 +1,15 @@ CONFIG_ARCH_AT91SAM9X5=y +CONFIG_AT91_MULTI_BOARDS=y +CONFIG_MACH_AT91SAM9X5EK=y CONFIG_BAREBOX_MAX_IMAGE_SIZE=0x40000 CONFIG_AEABI=y CONFIG_ARM_OPTIMZED_STRING_FUNCTIONS=y -CONFIG_PBL_IMAGE=y CONFIG_MMU=y CONFIG_MALLOC_SIZE=0xa00000 CONFIG_EXPERIMENTAL=y CONFIG_MALLOC_TLSF=y +CONFIG_KALLSYMS=y +CONFIG_RELOCATABLE=y CONFIG_PROMPT="9G20-EK:" CONFIG_GLOB=y CONFIG_PROMPT_HUSH_PS2="y" @@ -48,6 +51,7 @@ CONFIG_NET=y CONFIG_NET_NFS=y CONFIG_NET_NETCONSOLE=y +CONFIG_OF_BAREBOX_DRIVERS=y CONFIG_DRIVER_NET_MACB=y CONFIG_NET_USB=y CONFIG_NET_USB_ASIX=y @@ -71,6 +75,7 @@ CONFIG_MCI=y CONFIG_MCI_STARTUP=y CONFIG_MCI_ATMEL=y +CONFIG_MFD_SYSCON=y CONFIG_LED=y CONFIG_LED_GPIO=y CONFIG_LED_TRIGGERS=y @@ -85,3 +90,4 @@ CONFIG_FS_FAT=y CONFIG_FS_FAT_WRITE=y CONFIG_FS_FAT_LFN=y +CONFIG_LED_GPIO_OF=y diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile index 0c9e0e8..e8ad43b 100644 --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile @@ -91,5 +91,7 @@ vf610-zii-spu3-rev-a.dtb.o \ vf610-zii-scu4-aib-rev-c.dtb.o +pbl-dtb-$(CONFIG_MACH_AT91SAM9X5EK) += at91sam9x5ek.dtb.o + clean-files := *.dtb *.dtb.S .*.dtc .*.pre .*.dts *.dtb.lzo diff --git a/arch/arm/dts/at91sam9x5ek.dts b/arch/arm/dts/at91sam9x5ek.dts new file mode 100644 index 0000000..d5c7a8f --- /dev/null +++ b/arch/arm/dts/at91sam9x5ek.dts @@ -0,0 +1,70 @@ +/dts-v1/; + +#include +#include + +#include +#include +#include +#include +#include + +/ { + aliases { + mmc0 = &mmc0; + mmc1 = &mmc1; + }; + + i2c-gpio-0 { + status = "okay"; + }; + + ahb { + apb { + pinctrl@fffff400 { + spi { + pinctrl_board_spi: spi-board { + atmel,pins = ; + }; + }; + }; + }; + }; + + leds { + /* + * PB18 has a resource conflict since it is both used + * as a heartbeat LED and 1-wire bus in the kernel + * device tree. Because 1-wire EEPROMs contains + * importatnt revision information we move heartbeat + * to PD21 and remove the original pb18 node + */ + /delete-node/ pb18; + + pd21 { + linux,default-trigger = "heartbeat"; + }; + }; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_spi0>, <&pinctrl_board_spi>; +}; + +&i2c0 { + status = "disabled"; +}; + +/* + * This one conflicts with SPI NOR on the SoM + */ +&mmc1 { + status = "disabled"; +}; + +&macb0 { + status = "okay"; + phy-mode = "rmii"; +}; + diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig index 2d4721a..9db53b2 100644 --- a/arch/arm/mach-at91/Kconfig +++ b/arch/arm/mach-at91/Kconfig @@ -9,6 +9,26 @@ config HAVE_AT91_DBGU2 bool +config HAVE_AT91_UTMI + bool + +config HAVE_AT91_USB_CLK + bool + +config COMMON_CLK_AT91 + bool + select COMMON_CLK + select MFD_SYSCON + +config HAVE_AT91_SMD + bool + +config HAVE_AT91_H32MX + bool + +config HAVE_AT91_GENERATED_CLK + bool + config HAVE_AT91_LOWLEVEL_INIT bool @@ -20,6 +40,7 @@ select CPU_ARM926T select AT91SAM9_SMC select CLOCKSOURCE_ATMEL_PIT + select PINCTRL config SOC_SAMA5 bool @@ -106,6 +127,10 @@ select HAVE_AT91_DBGU0 select HAS_MACB select AT91SAM9G45_RESET + select HAVE_AT91_SMD + select HAVE_AT91_USB_CLK + select HAVE_AT91_UTMI + select COMMON_CLK_OF_PROVIDER help Select this if you are using one of Atmel's AT91SAM9x5 family SoC. This means that your SAM9 name finishes with a '5' (except if it is @@ -140,26 +165,32 @@ config ARCH_AT91SAM9263 bool "AT91SAM9263" select SOC_AT91SAM9263 + select HAVE_MACH_ARM_HEAD config ARCH_AT91SAM9G10 bool "AT91SAM9G10" select SOC_AT91SAM9261 + select HAVE_MACH_ARM_HEAD config ARCH_AT91SAM9G20 bool "AT91SAM9G20" select SOC_AT91SAM9260 + select HAVE_MACH_ARM_HEAD config ARCH_AT91SAM9G45 bool "AT91SAM9G45 or AT91SAM9M10" select SOC_AT91SAM9G45 + select HAVE_MACH_ARM_HEAD config ARCH_AT91SAM9X5 bool "AT91SAM9X5" select SOC_AT91SAM9X5 + select OFDEVICE config ARCH_AT91SAM9N12 bool "AT91SAM9N12" select SOC_AT91SAM9N12 + select HAVE_MACH_ARM_HEAD config ARCH_SAMA5D3 bool "SAMA5D3x" @@ -167,6 +198,7 @@ select HAVE_AT91_DBGU1 select HAS_MACB select AT91SAM9G45_RESET + select HAVE_MACH_ARM_HEAD config ARCH_SAMA5D4 bool "SAMA5D4" @@ -174,6 +206,7 @@ select HAVE_AT91_DBGU2 select HAS_MACB select AT91SAM9G45_RESET + select HAVE_MACH_ARM_HEAD endchoice @@ -429,23 +462,6 @@ # ---------------------------------------------------------- -if ARCH_AT91SAM9X5 - -choice - prompt "AT91SAM9x5 Series Board Type" - -config MACH_AT91SAM9X5EK - bool "Atmel AT91SAM9x5 Series Evaluation Kit" - help - Select this if you re using Atmel's AT91SAM9x5-EK Evaluation Kit. - Supported chips are sam9g15, sam9g25, sam9x25, sam9g35 and sam9x35. - -endchoice - -endif - -# ---------------------------------------------------------- - if ARCH_AT91SAM9N12 choice @@ -504,6 +520,22 @@ # ---------------------------------------------------------- + +config AT91_MULTI_BOARDS + bool "Allow multiple boards to be selected" + select HAVE_PBL_MULTI_IMAGES + +if AT91_MULTI_BOARDS + +config MACH_AT91SAM9X5EK + bool "Atmel AT91SAM9x5 Series Evaluation Kit" + depends on ARCH_AT91SAM9X5 + help + Select this if you re using Atmel's AT91SAM9x5-EK Evaluation Kit. + Supported chips are sam9g15, sam9g25, sam9x25, sam9g35 and sam9x35. + +endif + comment "AT91 Board Options" config MTD_AT91_DATAFLASH_CARD diff --git a/arch/arm/mach-at91/Makefile b/arch/arm/mach-at91/Makefile index 1f63b09..0892fb4 100644 --- a/arch/arm/mach-at91/Makefile +++ b/arch/arm/mach-at91/Makefile @@ -1,4 +1,9 @@ -obj-y += setup.o clock.o irq_fixup.o +obj-y += setup.o irq_fixup.o + +ifeq ($(CONFIG_COMMON_CLK_OF_PROVIDER),) +obj-y += clock.o +endif + obj-$(CONFIG_CMD_AT91_BOOT_TEST) += boot_test_cmd.o obj-$(CONFIG_AT91_BOOTSTRAP) += bootstrap.o @@ -28,7 +33,7 @@ obj-$(CONFIG_ARCH_AT91SAM9G10) += at91sam9261.o at91sam9261_devices.o obj-$(CONFIG_ARCH_AT91SAM9G20) += at91sam9260.o at91sam9260_devices.o obj-$(CONFIG_ARCH_AT91SAM9G45) += at91sam9g45.o at91sam9g45_devices.o -obj-$(CONFIG_ARCH_AT91SAM9X5) += at91sam9x5.o at91sam9x5_devices.o +obj-$(CONFIG_ARCH_AT91SAM9X5) += at91sam9x5_devices.o obj-$(CONFIG_ARCH_AT91SAM9N12) += at91sam9n12.o at91sam9n12_devices.o obj-$(CONFIG_ARCH_SAMA5D3) += sama5d3.o sama5d3_devices.o obj-$(CONFIG_ARCH_SAMA5D4) += sama5d4.o sama5d4_devices.o diff --git a/arch/arm/mach-at91/at91sam9x5.c b/arch/arm/mach-at91/at91sam9x5.c deleted file mode 100644 index 000b748..0000000 --- a/arch/arm/mach-at91/at91sam9x5.c +++ /dev/null @@ -1,311 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "soc.h" -#include "generic.h" -#include "clock.h" - -/* -------------------------------------------------------------------- - * Clocks - * -------------------------------------------------------------------- */ - -/* - * The peripheral clocks. - */ -static struct clk pioAB_clk = { - .name = "pioAB_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_PIOAB, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk pioCD_clk = { - .name = "pioCD_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_PIOCD, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk smd_clk = { - .name = "smd_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_SMD, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk usart0_clk = { - .name = "usart0_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_USART0, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk usart1_clk = { - .name = "usart1_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_USART1, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk usart2_clk = { - .name = "usart2_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_USART2, - .type = CLK_TYPE_PERIPHERAL, -}; -/* USART3 clock - Only for sam9g25/sam9x25 */ -static struct clk usart3_clk = { - .name = "usart3_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_USART3, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk twi0_clk = { - .name = "twi0_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_TWI0, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk twi1_clk = { - .name = "twi1_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_TWI1, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk twi2_clk = { - .name = "twi2_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_TWI2, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk mmc0_clk = { - .name = "mci0_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_MCI0, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk spi0_clk = { - .name = "spi0_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_SPI0, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk spi1_clk = { - .name = "spi1_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_SPI1, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk uart0_clk = { - .name = "uart0_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_UART0, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk uart1_clk = { - .name = "uart1_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_UART1, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk tcb0_clk = { - .name = "tcb0_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_TCB, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk pwm_clk = { - .name = "pwm_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_PWM, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk adc_clk = { - .name = "adc_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_ADC, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk dma0_clk = { - .name = "dma0_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_DMA0, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk dma1_clk = { - .name = "dma1_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_DMA1, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk uhphs_clk = { - .name = "uhphs", - .pmc_mask = 1 << AT91SAM9X5_ID_UHPHS, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk udphs_clk = { - .name = "udphs_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_UDPHS, - .type = CLK_TYPE_PERIPHERAL, -}; -/* emac0 clock - Only for sam9g25/sam9x25/sam9g35/sam9x35 */ -static struct clk macb0_clk = { - .name = "macb0_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_EMAC0, - .type = CLK_TYPE_PERIPHERAL, -}; -/* lcd clock - Only for sam9g15/sam9g35/sam9x35 */ -static struct clk lcdc_clk = { - .name = "lcdc_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_LCDC, - .type = CLK_TYPE_PERIPHERAL, -}; -/* isi clock - Only for sam9g25 */ -static struct clk isi_clk = { - .name = "isi_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_ISI, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk mmc1_clk = { - .name = "mci1_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_MCI1, - .type = CLK_TYPE_PERIPHERAL, -}; -/* emac1 clock - Only for sam9x25 */ -static struct clk macb1_clk = { - .name = "macb1_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_EMAC1, - .type = CLK_TYPE_PERIPHERAL, -}; -static struct clk ssc_clk = { - .name = "ssc_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_SSC, - .type = CLK_TYPE_PERIPHERAL, -}; -/* can0 clock - Only for sam9x35 */ -static struct clk can0_clk = { - .name = "can0_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_CAN0, - .type = CLK_TYPE_PERIPHERAL, -}; -/* can1 clock - Only for sam9x35 */ -static struct clk can1_clk = { - .name = "can1_clk", - .pmc_mask = 1 << AT91SAM9X5_ID_CAN1, - .type = CLK_TYPE_PERIPHERAL, -}; - -static struct clk *periph_clocks[] __initdata = { - &pioAB_clk, - &pioCD_clk, - &smd_clk, - &usart0_clk, - &usart1_clk, - &usart2_clk, - &twi0_clk, - &twi1_clk, - &twi2_clk, - &mmc0_clk, - &spi0_clk, - &spi1_clk, - &uart0_clk, - &uart1_clk, - &tcb0_clk, - &pwm_clk, - &adc_clk, - &dma0_clk, - &dma1_clk, - &uhphs_clk, - &udphs_clk, - &mmc1_clk, - &ssc_clk, - // irq0 -}; - -static struct clk_lookup periph_clocks_lookups[] = { - CLKDEV_CON_DEV_ID("macb_clk", "macb0", &macb0_clk), - CLKDEV_CON_DEV_ID("macb_clk", "macb1", &macb1_clk), - CLKDEV_CON_ID("ohci_clk", &uhphs_clk), - CLKDEV_CON_DEV_ID("ehci_clk", "atmel-ehci", &uhphs_clk), - CLKDEV_CON_DEV_ID("spi_clk", "atmel_spi0", &spi0_clk), - CLKDEV_CON_DEV_ID("spi_clk", "atmel_spi1", &spi1_clk), - CLKDEV_CON_DEV_ID("mci_clk", "atmel_mci0", &mmc0_clk), - CLKDEV_CON_DEV_ID("mci_clk", "atmel_mci1", &mmc1_clk), - CLKDEV_DEV_ID("at91sam9x5-gpio0", &pioAB_clk), - CLKDEV_DEV_ID("at91sam9x5-gpio1", &pioAB_clk), - CLKDEV_DEV_ID("at91sam9x5-gpio2", &pioCD_clk), - CLKDEV_DEV_ID("at91sam9x5-gpio3", &pioCD_clk), - CLKDEV_DEV_ID("at91-pit", &mck), - CLKDEV_CON_DEV_ID("hck1", "atmel_hlcdfb", &lcdc_clk), -}; - -static struct clk_lookup usart_clocks_lookups[] = { - CLKDEV_CON_DEV_ID("usart", "atmel_usart0", &mck), - CLKDEV_CON_DEV_ID("usart", "atmel_usart1", &usart0_clk), - CLKDEV_CON_DEV_ID("usart", "atmel_usart2", &usart1_clk), - CLKDEV_CON_DEV_ID("usart", "atmel_usart3", &usart2_clk), - CLKDEV_CON_DEV_ID("usart", "atmel_usart4", &usart3_clk), -}; - -/* - * The two programmable clocks. - * You must configure pin multiplexing to bring these signals out. - */ -static struct clk pck0 = { - .name = "pck0", - .pmc_mask = AT91_PMC_PCK0, - .type = CLK_TYPE_PROGRAMMABLE, - .id = 0, -}; -static struct clk pck1 = { - .name = "pck1", - .pmc_mask = AT91_PMC_PCK1, - .type = CLK_TYPE_PROGRAMMABLE, - .id = 1, -}; - -static void __init at91sam9x5_register_clocks(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(periph_clocks); i++) - clk_register(periph_clocks[i]); - - clkdev_add_table(periph_clocks_lookups, - ARRAY_SIZE(periph_clocks_lookups)); - clkdev_add_table(usart_clocks_lookups, - ARRAY_SIZE(usart_clocks_lookups)); - - if (cpu_is_at91sam9g25() - || cpu_is_at91sam9x25()) - clk_register(&usart3_clk); - - if (cpu_is_at91sam9g25() - || cpu_is_at91sam9x25() - || cpu_is_at91sam9g35() - || cpu_is_at91sam9x35()) - clk_register(&macb0_clk); - - if (cpu_is_at91sam9g15() - || cpu_is_at91sam9g35() - || cpu_is_at91sam9x35()) - clk_register(&lcdc_clk); - - if (cpu_is_at91sam9g25()) - clk_register(&isi_clk); - - if (cpu_is_at91sam9x25()) - clk_register(&macb1_clk); - - if (cpu_is_at91sam9x25() - || cpu_is_at91sam9x35()) { - clk_register(&can0_clk); - clk_register(&can1_clk); - } - - clk_register(&pck0); - clk_register(&pck1); -} - -/* -------------------------------------------------------------------- - * AT91SAM9x5 processor initialization - * -------------------------------------------------------------------- */ - -static void at91sam9x5_initialize(void) -{ - /* Register the processor-specific clocks */ - at91sam9x5_register_clocks(); - - /* Register GPIO subsystem */ - at91_add_sam9x5_gpio(0, AT91SAM9X5_BASE_PIOA); - at91_add_sam9x5_gpio(1, AT91SAM9X5_BASE_PIOB); - at91_add_sam9x5_gpio(2, AT91SAM9X5_BASE_PIOC); - at91_add_sam9x5_gpio(3, AT91SAM9X5_BASE_PIOD); - - at91_add_pit(AT91SAM9X5_BASE_PIT); - at91_add_sam9_smc(DEVICE_ID_SINGLE, AT91SAM9X5_BASE_SMC, 0x200); -} - -AT91_SOC_START(sam9x5) - .init = at91sam9x5_initialize, -AT91_SOC_END diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h index 491b220..5d76e00 100644 --- a/arch/arm/mach-at91/include/mach/board.h +++ b/arch/arm/mach-at91/include/mach/board.h @@ -32,11 +32,13 @@ void at91_set_main_clock(unsigned long rate); +#define AT91_MAX_USBH_PORTS 3 + /* USB Host */ struct at91_usbh_data { u8 ports; /* number of ports on root hub */ - int vbus_pin[2]; /* port power-control pin */ - u8 vbus_pin_active_low[2]; /* vbus polarity */ + int vbus_pin[AT91_MAX_USBH_PORTS]; /* port power-control pin */ + u8 vbus_pin_active_low[AT91_MAX_USBH_PORTS]; /* vbus polarity */ }; extern void __init at91_add_device_usbh_ohci(struct at91_usbh_data *data); extern void __init at91_add_device_usbh_ehci(struct at91_usbh_data *data); diff --git a/arch/arm/mach-at91/setup.c b/arch/arm/mach-at91/setup.c index 1fa50ac..8f32af0 100644 --- a/arch/arm/mach-at91/setup.c +++ b/arch/arm/mach-at91/setup.c @@ -85,7 +85,6 @@ case ARCH_ID_AT91SAM9X5: at91_soc_initdata.type = AT91_SOC_SAM9X5; - at91_boot_soc = at91sam9x5_soc; break; case ARCH_ID_AT91SAM9N12: @@ -284,6 +283,9 @@ pr_info("AT91: Detected soc subtype: %s\n", at91_get_soc_subtype(&at91_soc_initdata)); + if (IS_ENABLED(CONFIG_COMMON_CLK_OF_PROVIDER)) + return 0; + if (!at91_soc_is_enabled()) panic("AT91: Soc not enabled"); diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 5811d28..d75b954 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_CLK_SOCFPGA) += socfpga.o obj-$(CONFIG_MACH_MIPS_ATH79) += clk-ar933x.o obj-$(CONFIG_ARCH_IMX) += imx/ +obj-$(CONFIG_COMMON_CLK_AT91) += at91/ diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile new file mode 100644 index 0000000..13e67bd --- /dev/null +++ b/drivers/clk/at91/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for at91 specific clk +# + +obj-y += pmc.o sckc.o +obj-y += clk-slow.o clk-main.o clk-pll.o clk-plldiv.o clk-master.o +obj-y += clk-system.o clk-peripheral.o clk-programmable.o + +obj-$(CONFIG_HAVE_AT91_UTMI) += clk-utmi.o +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 diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c new file mode 100644 index 0000000..4e1cd5a --- /dev/null +++ b/drivers/clk/at91/clk-generated.c @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2015 Atmel Corporation, + * Nicolas Ferre + * + * Based on clk-programmable & clk-peripheral drivers by Boris BREZILLON. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "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 regmap *regmap; + struct clk_range range; + spinlock_t *lock; + u32 id; + u32 gckdiv; + u8 parent_id; +}; + +#define to_clk_generated(hw) \ + container_of(hw, struct clk_generated, hw) + +static int clk_generated_enable(struct clk_hw *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, + AT91_PMC_PCR_GCKDIV_MASK | AT91_PMC_PCR_GCKCSS_MASK | + AT91_PMC_PCR_CMD | AT91_PMC_PCR_GCKEN, + AT91_PMC_PCR_GCKCSS(gck->parent_id) | + 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) +{ + 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) +{ + 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, + unsigned long parent_rate) +{ + struct clk_generated *gck = to_clk_generated(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) +{ + struct clk_generated *gck = to_clk_generated(hw); + + if (index >= clk_hw_get_num_parents(hw)) + return -EINVAL; + + gck->parent_id = index; + return 0; +} + +static u8 clk_generated_get_parent(struct clk_hw *hw) +{ + struct clk_generated *gck = to_clk_generated(hw); + + return gck->parent_id; +} + +/* No modification of hardware as we have the flag CLK_SET_RATE_GATE set */ +static int clk_generated_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct clk_generated *gck = to_clk_generated(hw); + u32 div; + + if (!rate) + return -EINVAL; + + if (gck->range.max && rate > gck->range.max) + return -EINVAL; + + div = DIV_ROUND_CLOSEST(parent_rate, rate); + if (div > GENERATED_MAX_DIV + 1 || !div) + return -EINVAL; + + gck->gckdiv = div - 1; + return 0; +} + +static const struct clk_ops generated_ops = { + .enable = clk_generated_enable, + .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, +}; + +/** + * clk_generated_startup - Initialize a given clock to its default parent and + * divisor parameter. + * + * @gck: Generated clock to set the startup parameters for. + * + * Take parameters from the hardware and update local clock configuration + * accordingly. + */ +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; + gck->gckdiv = (tmp & AT91_PMC_PCR_GCKDIV_MASK) + >> AT91_PMC_PCR_GCKDIV_OFFSET; +} + +static struct clk_hw * __init +at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock, + const char *name, const char **parent_names, + u8 num_parents, u8 id, + const struct clk_range *range) +{ + struct clk_generated *gck; + struct clk_init_data init; + struct clk_hw *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->regmap = regmap; + gck->lock = lock; + gck->range = *range; + + hw = &gck->hw; + ret = clk_hw_register(NULL, &gck->hw); + if (ret) { + kfree(gck); + hw = ERR_PTR(ret); + } else + clk_generated_startup(gck); + + 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 new file mode 100644 index 0000000..e0daa4a --- /dev/null +++ b/drivers/clk/at91/clk-h32mx.c @@ -0,0 +1,125 @@ +/* + * clk-h32mx.c + * + * Copyright (C) 2014 Atmel + * + * Alexandre Belloni + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +#define H32MX_MAX_FREQ 90000000 + +struct clk_sama5d4_h32mx { + struct clk_hw hw; + struct regmap *regmap; +}; + +#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, + unsigned long parent_rate) +{ + struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw); + unsigned int mckr; + + regmap_read(h32mxclk->regmap, AT91_PMC_MCKR, &mckr); + if (mckr & AT91_PMC_H32MXDIV) + return parent_rate / 2; + + if (parent_rate > H32MX_MAX_FREQ) + pr_warn("H32MX clock is too fast\n"); + return parent_rate; +} + +static long clk_sama5d4_h32mx_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long div; + + if (rate > *parent_rate) + return *parent_rate; + div = *parent_rate / 2; + if (rate < div) + return div; + + if (rate - div < *parent_rate - rate) + return div; + + return *parent_rate; +} + +static int clk_sama5d4_h32mx_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw); + u32 mckr = 0; + + if (parent_rate != rate && (parent_rate / 2) != rate) + return -EINVAL; + + if ((parent_rate / 2) == rate) + mckr = AT91_PMC_H32MXDIV; + + regmap_update_bits(h32mxclk->regmap, AT91_PMC_MCKR, + AT91_PMC_H32MXDIV, mckr); + + return 0; +} + +static const struct clk_ops h32mx_ops = { + .recalc_rate = clk_sama5d4_h32mx_recalc_rate, + .round_rate = clk_sama5d4_h32mx_round_rate, + .set_rate = clk_sama5d4_h32mx_set_rate, +}; + +static void __init of_sama5d4_clk_h32mx_setup(struct device_node *np) +{ + 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); + + 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->hw.init = &init; + h32mxclk->regmap = regmap; + + ret = clk_hw_register(NULL, &h32mxclk->hw); + if (ret) { + kfree(h32mxclk); + return; + } + + of_clk_add_hw_provider(np, of_clk_hw_simple_get, &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 new file mode 100644 index 0000000..55bc618 --- /dev/null +++ b/drivers/clk/at91/clk-main.c @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +#define SLOW_CLOCK_FREQ 32768 +#define MAINF_DIV 16 +#define MAINFRDY_TIMEOUT (((MAINF_DIV + 1) * SECOND) / \ + SLOW_CLOCK_FREQ) +#define MAINF_LOOP_MIN_WAIT (SECOND / SLOW_CLOCK_FREQ) +#define MAINF_LOOP_MAX_WAIT MAINFRDY_TIMEOUT + +#define MOR_KEY_MASK (0xff << 16) + +struct clk_main_osc { + struct clk clk; + struct regmap *regmap; + const char *parent; +}; + +#define to_clk_main_osc(clk) container_of(clk, struct clk_main_osc, clk) + +struct clk_main_rc_osc { + struct clk clk; + struct regmap *regmap; + unsigned long frequency; +}; + +#define to_clk_main_rc_osc(clk) container_of(clk, struct clk_main_rc_osc, clk) + +struct clk_rm9200_main { + struct clk clk; + struct regmap *regmap; + const char *parent; +}; + +#define to_clk_rm9200_main(clk) container_of(clk, struct clk_rm9200_main, clk) + +struct clk_sam9x5_main { + struct clk clk; + struct regmap *regmap; + u8 parent; +}; + +#define to_clk_sam9x5_main(clk) container_of(clk, struct clk_sam9x5_main, clk) + +static inline bool clk_main_osc_ready(struct regmap *regmap) +{ + unsigned int status; + + regmap_read(regmap, AT91_PMC_SR, &status); + + return status & AT91_PMC_MOSCS; +} + +static int clk_main_osc_enable(struct clk *clk) +{ + struct clk_main_osc *osc = to_clk_main_osc(clk); + struct regmap *regmap = osc->regmap; + u32 tmp; + + regmap_read(regmap, AT91_CKGR_MOR, &tmp); + tmp &= ~MOR_KEY_MASK; + + if (tmp & AT91_PMC_OSCBYPASS) + return 0; + + if (!(tmp & AT91_PMC_MOSCEN)) { + tmp |= AT91_PMC_MOSCEN | AT91_PMC_KEY; + regmap_write(regmap, AT91_CKGR_MOR, tmp); + } + + while (!clk_main_osc_ready(regmap)) + barrier(); + + return 0; +} + +static void clk_main_osc_disable(struct clk *clk) +{ + struct clk_main_osc *osc = to_clk_main_osc(clk); + struct regmap *regmap = osc->regmap; + u32 tmp; + + regmap_read(regmap, AT91_CKGR_MOR, &tmp); + if (tmp & AT91_PMC_OSCBYPASS) + return; + + if (!(tmp & AT91_PMC_MOSCEN)) + return; + + tmp &= ~(AT91_PMC_KEY | AT91_PMC_MOSCEN); + regmap_write(regmap, AT91_CKGR_MOR, tmp | AT91_PMC_KEY); +} + +static int clk_main_osc_is_enabled(struct clk *clk) +{ + struct clk_main_osc *osc = to_clk_main_osc(clk); + struct regmap *regmap = osc->regmap; + u32 tmp, status; + + regmap_read(regmap, AT91_CKGR_MOR, &tmp); + if (tmp & AT91_PMC_OSCBYPASS) + return 1; + + regmap_read(regmap, AT91_PMC_SR, &status); + + return (status & AT91_PMC_MOSCS) && (tmp & AT91_PMC_MOSCEN); +} + +static const struct clk_ops main_osc_ops = { + .enable = clk_main_osc_enable, + .disable = clk_main_osc_disable, + .is_enabled = clk_main_osc_is_enabled, +}; + +static struct clk * +at91_clk_register_main_osc(struct regmap *regmap, + const char *name, + const char *parent_name, + bool bypass) +{ + struct clk_main_osc *osc; + int ret; + + if (!name || !parent_name) + return ERR_PTR(-EINVAL); + + osc = xzalloc(sizeof(*osc)); + + osc->parent = parent_name; + osc->clk.name = name; + osc->clk.ops = &main_osc_ops; + osc->clk.parent_names = &osc->parent; + osc->clk.num_parents = 1; + osc->regmap = regmap; + + if (bypass) + regmap_write_bits(regmap, + AT91_CKGR_MOR, MOR_KEY_MASK | + AT91_PMC_MOSCEN, + AT91_PMC_OSCBYPASS | AT91_PMC_KEY); + + ret = clk_register(&osc->clk); + if (ret) { + free(osc); + return ERR_PTR(ret); + } + + 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; + + regmap_read(regmap, AT91_PMC_SR, &status); + + return status & AT91_PMC_MOSCRCS; +} + +static int clk_main_rc_osc_enable(struct clk *clk) +{ + struct clk_main_rc_osc *osc = to_clk_main_rc_osc(clk); + struct regmap *regmap = osc->regmap; + unsigned int mor; + + regmap_read(regmap, AT91_CKGR_MOR, &mor); + + if (!(mor & AT91_PMC_MOSCRCEN)) + regmap_write_bits(regmap, AT91_CKGR_MOR, + MOR_KEY_MASK | AT91_PMC_MOSCRCEN, + AT91_PMC_MOSCRCEN | AT91_PMC_KEY); + + while (!clk_main_rc_osc_ready(regmap)) + barrier(); + + return 0; +} + +static void clk_main_rc_osc_disable(struct clk *clk) +{ + struct clk_main_rc_osc *osc = to_clk_main_rc_osc(clk); + struct regmap *regmap = osc->regmap; + unsigned int mor; + + regmap_read(regmap, AT91_CKGR_MOR, &mor); + + if (!(mor & AT91_PMC_MOSCRCEN)) + return; + + regmap_write_bits(regmap, AT91_CKGR_MOR, + MOR_KEY_MASK | AT91_PMC_MOSCRCEN, AT91_PMC_KEY); +} + +static int clk_main_rc_osc_is_enabled(struct clk *clk) +{ + struct clk_main_rc_osc *osc = to_clk_main_rc_osc(clk); + struct regmap *regmap = osc->regmap; + unsigned int mor, status; + + regmap_read(regmap, AT91_CKGR_MOR, &mor); + regmap_read(regmap, AT91_PMC_SR, &status); + + return (mor & AT91_PMC_MOSCRCEN) && (status & AT91_PMC_MOSCRCS); +} + +static unsigned long clk_main_rc_osc_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_main_rc_osc *osc = to_clk_main_rc_osc(clk); + + return osc->frequency; +} + +static const struct clk_ops main_rc_osc_ops = { + .enable = clk_main_rc_osc_enable, + .disable = clk_main_rc_osc_disable, + .is_enabled = clk_main_rc_osc_is_enabled, + .recalc_rate = clk_main_rc_osc_recalc_rate, +}; + +static struct clk * +at91_clk_register_main_rc_osc(struct regmap *regmap, + const char *name, + u32 frequency) +{ + int ret; + struct clk_main_rc_osc *osc; + + if (!name || !frequency) + return ERR_PTR(-EINVAL); + + osc = xzalloc(sizeof(*osc)); + + osc->clk.name = name; + osc->clk.ops = &main_rc_osc_ops; + osc->clk.parent_names = NULL; + osc->clk.num_parents = 0; + + osc->regmap = regmap; + osc->frequency = frequency; + + ret = clk_register(&osc->clk); + if (ret) { + kfree(osc); + return ERR_PTR(ret); + } + + 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; + uint64_t start = get_time_ns(); + + do { + regmap_read(regmap, AT91_CKGR_MCFR, &mcfr); + if (mcfr & AT91_PMC_MAINRDY) + return 0; + } while (!is_timeout(start, MAINFRDY_TIMEOUT * USECOND)); + + return -ETIMEDOUT; +} + +static unsigned long clk_main_recalc_rate(struct regmap *regmap, + unsigned long parent_rate) +{ + unsigned int mcfr; + + if (parent_rate) + return parent_rate; + + pr_warn("Main crystal frequency not set, using approximate value\n"); + regmap_read(regmap, AT91_CKGR_MCFR, &mcfr); + if (!(mcfr & AT91_PMC_MAINRDY)) + return 0; + + return ((mcfr & AT91_PMC_MAINF) * SLOW_CLOCK_FREQ) / MAINF_DIV; +} + +static int clk_rm9200_main_enable(struct clk *clk) +{ + struct clk_rm9200_main *clkmain = to_clk_rm9200_main(clk); + + return clk_main_probe_frequency(clkmain->regmap); +} + +static int clk_rm9200_main_is_enabled(struct clk *clk) +{ + struct clk_rm9200_main *clkmain = to_clk_rm9200_main(clk); + unsigned int status; + + regmap_read(clkmain->regmap, AT91_CKGR_MCFR, &status); + + return status & AT91_PMC_MAINRDY ? 1 : 0; +} + +static unsigned long clk_rm9200_main_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_rm9200_main *clkmain = to_clk_rm9200_main(clk); + + return clk_main_recalc_rate(clkmain->regmap, parent_rate); +} + +static const struct clk_ops rm9200_main_ops = { + .enable = clk_rm9200_main_enable, + .is_enabled = clk_rm9200_main_is_enabled, + .recalc_rate = clk_rm9200_main_recalc_rate, +}; + +static struct clk * +at91_clk_register_rm9200_main(struct regmap *regmap, + const char *name, + const char *parent_name) +{ + int ret; + struct clk_rm9200_main *clkmain; + + if (!name) + return ERR_PTR(-EINVAL); + + if (!parent_name) + return ERR_PTR(-EINVAL); + + clkmain = xzalloc(sizeof(*clkmain)); + + clkmain->clk.name = name; + clkmain->clk.ops = &rm9200_main_ops; + clkmain->clk.parent_names = &clkmain->parent; + clkmain->clk.num_parents = 1; + clkmain->regmap = regmap; + + ret = clk_register(&clkmain->clk); + if (ret) { + kfree(clkmain); + return ERR_PTR(ret); + } + + 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; + + regmap_read(regmap, AT91_PMC_SR, &status); + + return status & AT91_PMC_MOSCSELS ? 1 : 0; +} + +static int clk_sam9x5_main_enable(struct clk *clk) +{ + struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(clk); + struct regmap *regmap = clkmain->regmap; + + while (!clk_sam9x5_main_ready(regmap)) + barrier(); + + return clk_main_probe_frequency(regmap); +} + +static int clk_sam9x5_main_is_enabled(struct clk *clk) +{ + struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(clk); + + return clk_sam9x5_main_ready(clkmain->regmap); +} + +static unsigned long clk_sam9x5_main_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(clk); + + return clk_main_recalc_rate(clkmain->regmap, parent_rate); +} + +static int clk_sam9x5_main_set_parent(struct clk *clk, u8 index) +{ + struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(clk); + struct regmap *regmap = clkmain->regmap; + unsigned int tmp; + + if (index > 1) + return -EINVAL; + + regmap_read(regmap, AT91_CKGR_MOR, &tmp); + tmp &= ~MOR_KEY_MASK; + + if (index && !(tmp & AT91_PMC_MOSCSEL)) + regmap_write(regmap, AT91_CKGR_MOR, tmp | AT91_PMC_MOSCSEL); + else if (!index && (tmp & AT91_PMC_MOSCSEL)) + regmap_write(regmap, AT91_CKGR_MOR, tmp & ~AT91_PMC_MOSCSEL); + + while (!clk_sam9x5_main_ready(regmap)) + barrier(); + + return 0; +} + +static int clk_sam9x5_main_get_parent(struct clk *clk) +{ + struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(clk); + unsigned int status; + + regmap_read(clkmain->regmap, AT91_CKGR_MOR, &status); + + return status & AT91_PMC_MOSCEN ? 1 : 0; +} + +static const struct clk_ops sam9x5_main_ops = { + .enable = clk_sam9x5_main_enable, + .is_enabled = clk_sam9x5_main_is_enabled, + .recalc_rate = clk_sam9x5_main_recalc_rate, + .set_parent = clk_sam9x5_main_set_parent, + .get_parent = clk_sam9x5_main_get_parent, +}; + +static struct clk * +at91_clk_register_sam9x5_main(struct regmap *regmap, + const char *name, + const char **parent_names, + int num_parents) +{ + int ret; + unsigned int status; + size_t parents_array_size; + struct clk_sam9x5_main *clkmain; + + if (!name) + return ERR_PTR(-EINVAL); + + if (!parent_names || !num_parents) + return ERR_PTR(-EINVAL); + + clkmain = xzalloc(sizeof(*clkmain)); + + clkmain->clk.name = name; + clkmain->clk.ops = &sam9x5_main_ops; + parents_array_size = num_parents * sizeof (clkmain->clk.parent_names[0]); + clkmain->clk.parent_names = xzalloc(parents_array_size); + memcpy(clkmain->clk.parent_names, parent_names, parents_array_size); + clkmain->clk.num_parents = num_parents; + + /* init.flags = CLK_SET_PARENT_GATE; */ + + clkmain->regmap = regmap; + regmap_read(clkmain->regmap, AT91_CKGR_MOR, &status); + clkmain->parent = status & AT91_PMC_MOSCEN ? 1 : 0; + + ret = clk_register(&clkmain->clk); + if (ret) { + kfree(clkmain); + return ERR_PTR(ret); + } + + 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 new file mode 100644 index 0000000..b3a50ce --- /dev/null +++ b/drivers/clk/at91/clk-master.c @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +#define MASTER_SOURCE_MAX 4 + +#define MASTER_PRES_MASK 0x7 +#define MASTER_PRES_MAX MASTER_PRES_MASK +#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 { + struct clk clk; + struct regmap *regmap; + const struct clk_master_layout *layout; + const struct clk_master_characteristics *characteristics; + const char *parents[MASTER_SOURCE_MAX]; +}; + +static inline bool clk_master_ready(struct regmap *regmap) +{ + unsigned int status; + + regmap_read(regmap, AT91_PMC_SR, &status); + + return status & AT91_PMC_MCKRDY ? 1 : 0; +} + +static int clk_master_enable(struct clk *clk) +{ + struct clk_master *master = to_clk_master(clk); + + while (!clk_master_ready(master->regmap)) + barrier(); + + return 0; +} + +static int clk_master_is_enabled(struct clk *clk) +{ + struct clk_master *master = to_clk_master(clk); + + return clk_master_ready(master->regmap); +} + +static unsigned long clk_master_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + u8 pres; + u8 div; + unsigned long rate = parent_rate; + struct clk_master *master = to_clk_master(clk); + const struct clk_master_layout *layout = master->layout; + const struct clk_master_characteristics *characteristics = + master->characteristics; + unsigned int mckr; + + regmap_read(master->regmap, AT91_PMC_MCKR, &mckr); + mckr &= layout->mask; + + pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK; + div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK; + + if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX) + rate /= 3; + else + rate >>= pres; + + rate /= characteristics->divisors[div]; + + if (rate < characteristics->output.min) + pr_warn("master clk is underclocked"); + else if (rate > characteristics->output.max) + pr_warn("master clk is overclocked"); + + return rate; +} + +static int clk_master_get_parent(struct clk *clk) +{ + struct clk_master *master = to_clk_master(clk); + unsigned int mckr; + + regmap_read(master->regmap, AT91_PMC_MCKR, &mckr); + + return mckr & AT91_PMC_CSS; +} + +static const struct clk_ops master_ops = { + .enable = clk_master_enable, + .is_enabled = clk_master_is_enabled, + .recalc_rate = clk_master_recalc_rate, + .get_parent = clk_master_get_parent, +}; + +static 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) +{ + int ret; + const size_t parent_names_size = num_parents * sizeof(parent_names[0]); + struct clk_master *master; + + if (!name || !num_parents || !parent_names) + return ERR_PTR(-EINVAL); + + master = xzalloc(sizeof(*master)); + + master->clk.name = name; + master->clk.ops = &master_ops; + memcpy(master->parents, parent_names, parent_names_size); + master->clk.parent_names = master->parents; + master->clk.num_parents = num_parents; + + master->layout = layout; + master->characteristics = characteristics; + master->regmap = regmap; + + ret = clk_register(&master->clk); + if (ret) { + kfree(master); + return ERR_PTR(ret); + } + + return &master->clk; +} + + +static const struct clk_master_layout at91rm9200_master_layout = { + .mask = 0x31F, + .pres_shift = 2, +}; + +static 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 new file mode 100644 index 0000000..bbe6ffa --- /dev/null +++ b/drivers/clk/at91/clk-peripheral.c @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "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)) + +#define PERIPHERAL_RSHIFT_MASK 0x3 +#define PERIPHERAL_RSHIFT(val) (((val) >> 16) & PERIPHERAL_RSHIFT_MASK) + +#define PERIPHERAL_MAX_SHIFT 3 + +struct clk_peripheral { + struct clk clk; + struct regmap *regmap; + u32 id; + const char *parent; +}; + +#define to_clk_peripheral(clk) container_of(clk, struct clk_peripheral, clk) + +struct clk_sam9x5_peripheral { + struct clk clk; + struct regmap *regmap; + struct clk_range range; + u32 id; + u32 div; + bool auto_div; + const char *parent; +}; + +#define to_clk_sam9x5_peripheral(clk) \ + container_of(clk, struct clk_sam9x5_peripheral, clk) + +static int clk_peripheral_enable(struct clk *clk) +{ + struct clk_peripheral *periph = to_clk_peripheral(clk); + int offset = AT91_PMC_PCER; + u32 id = periph->id; + + if (id < PERIPHERAL_ID_MIN) + return 0; + if (id > PERIPHERAL_ID_MAX) + offset = AT91_PMC_PCER1; + regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id)); + + return 0; +} + +static void clk_peripheral_disable(struct clk *clk) +{ + struct clk_peripheral *periph = to_clk_peripheral(clk); + int offset = AT91_PMC_PCDR; + u32 id = periph->id; + + if (id < PERIPHERAL_ID_MIN) + return; + if (id > PERIPHERAL_ID_MAX) + offset = AT91_PMC_PCDR1; + regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id)); +} + +static int clk_peripheral_is_enabled(struct clk *clk) +{ + struct clk_peripheral *periph = to_clk_peripheral(clk); + int offset = AT91_PMC_PCSR; + unsigned int status; + u32 id = periph->id; + + if (id < PERIPHERAL_ID_MIN) + return 1; + if (id > PERIPHERAL_ID_MAX) + offset = AT91_PMC_PCSR1; + regmap_read(periph->regmap, offset, &status); + + return status & PERIPHERAL_MASK(id) ? 1 : 0; +} + +static const struct clk_ops peripheral_ops = { + .enable = clk_peripheral_enable, + .disable = clk_peripheral_disable, + .is_enabled = clk_peripheral_is_enabled, +}; + +static struct clk * +at91_clk_register_peripheral(struct regmap *regmap, const char *name, + const char *parent_name, u32 id) +{ + int ret; + struct clk_peripheral *periph; + + if (!name || !parent_name || id > PERIPHERAL_ID_MAX) + return ERR_PTR(-EINVAL); + + periph = xzalloc(sizeof(*periph)); + + periph->clk.name = name; + periph->clk.ops = &peripheral_ops; + + if (parent_name) { + periph->parent = parent_name; + periph->clk.parent_names = &periph->parent; + periph->clk.num_parents = 1; + } + + periph->id = id; + periph->regmap = regmap; + + ret = clk_register(&periph->clk); + if (ret) { + kfree(periph); + return ERR_PTR(ret); + } + + return &periph->clk; +} + +static void clk_sam9x5_peripheral_autodiv(struct clk_sam9x5_peripheral *periph) +{ + struct clk *parent; + unsigned long parent_rate; + int shift = 0; + + if (!periph->auto_div) + return; + + if (periph->range.max) { + parent = clk_get_parent(&periph->clk); + parent_rate = clk_get_rate(parent); + if (!parent_rate) + return; + + for (; shift < PERIPHERAL_MAX_SHIFT; shift++) { + if (parent_rate >> shift <= periph->range.max) + break; + } + } + + periph->auto_div = false; + periph->div = shift; +} + +static int clk_sam9x5_peripheral_enable(struct clk *clk) +{ + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk); + + if (periph->id < PERIPHERAL_ID_MIN) + return 0; + + regmap_write(periph->regmap, AT91_PMC_PCR, + (periph->id & AT91_PMC_PCR_PID_MASK)); + regmap_write_bits(periph->regmap, AT91_PMC_PCR, + AT91_PMC_PCR_DIV_MASK | AT91_PMC_PCR_CMD | + AT91_PMC_PCR_EN, + AT91_PMC_PCR_DIV(periph->div) | + AT91_PMC_PCR_CMD | + AT91_PMC_PCR_EN); + + return 0; +} + +static void clk_sam9x5_peripheral_disable(struct clk *clk) +{ + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk); + + if (periph->id < PERIPHERAL_ID_MIN) + return; + + regmap_write(periph->regmap, AT91_PMC_PCR, + (periph->id & AT91_PMC_PCR_PID_MASK)); + regmap_write_bits(periph->regmap, AT91_PMC_PCR, + AT91_PMC_PCR_EN | AT91_PMC_PCR_CMD, + AT91_PMC_PCR_CMD); +} + +static int clk_sam9x5_peripheral_is_enabled(struct clk *clk) +{ + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk); + unsigned int status; + + if (periph->id < PERIPHERAL_ID_MIN) + return 1; + + regmap_write(periph->regmap, AT91_PMC_PCR, + (periph->id & AT91_PMC_PCR_PID_MASK)); + regmap_read(periph->regmap, AT91_PMC_PCR, &status); + + return status & AT91_PMC_PCR_EN ? 1 : 0; +} + +static unsigned long +clk_sam9x5_peripheral_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk); + unsigned int status; + + if (periph->id < PERIPHERAL_ID_MIN) + return parent_rate; + + regmap_write(periph->regmap, AT91_PMC_PCR, + (periph->id & AT91_PMC_PCR_PID_MASK)); + regmap_read(periph->regmap, AT91_PMC_PCR, &status); + + if (status & AT91_PMC_PCR_EN) { + periph->div = PERIPHERAL_RSHIFT(status); + periph->auto_div = false; + } else { + clk_sam9x5_peripheral_autodiv(periph); + } + + return parent_rate >> periph->div; +} + +static long clk_sam9x5_peripheral_round_rate(struct clk *clk, + unsigned long rate, + unsigned long *parent_rate) +{ + int shift = 0; + unsigned long best_rate; + unsigned long best_diff; + unsigned long cur_rate = *parent_rate; + unsigned long cur_diff; + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk); + + if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) + return *parent_rate; + + if (periph->range.max) { + for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) { + cur_rate = *parent_rate >> shift; + if (cur_rate <= periph->range.max) + break; + } + } + + if (rate >= cur_rate) + return cur_rate; + + best_diff = cur_rate - rate; + best_rate = cur_rate; + for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) { + cur_rate = *parent_rate >> shift; + if (cur_rate < rate) + cur_diff = rate - cur_rate; + else + cur_diff = cur_rate - rate; + + if (cur_diff < best_diff) { + best_diff = cur_diff; + best_rate = cur_rate; + } + + if (!best_diff || cur_rate < rate) + break; + } + + return best_rate; +} + +static int clk_sam9x5_peripheral_set_rate(struct clk *clk, + unsigned long rate, + unsigned long parent_rate) +{ + int shift; + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk); + if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) { + if (parent_rate == rate) + return 0; + else + return -EINVAL; + } + + if (periph->range.max && rate > periph->range.max) + return -EINVAL; + + for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) { + if (parent_rate >> shift == rate) { + periph->auto_div = false; + periph->div = shift; + return 0; + } + } + + return -EINVAL; +} + +static const struct clk_ops sam9x5_peripheral_ops = { + .enable = clk_sam9x5_peripheral_enable, + .disable = clk_sam9x5_peripheral_disable, + .is_enabled = clk_sam9x5_peripheral_is_enabled, + .recalc_rate = clk_sam9x5_peripheral_recalc_rate, + .round_rate = clk_sam9x5_peripheral_round_rate, + .set_rate = clk_sam9x5_peripheral_set_rate, +}; + +static struct clk * +at91_clk_register_sam9x5_peripheral(struct regmap *regmap, + const char *name, const char *parent_name, + u32 id, const struct clk_range *range) +{ + int ret; + struct clk_sam9x5_peripheral *periph; + + if (!name || !parent_name) + return ERR_PTR(-EINVAL); + + periph = xzalloc(sizeof(*periph)); + + periph->clk.name = name; + periph->clk.ops = &sam9x5_peripheral_ops; + + if (parent_name) { + periph->parent = parent_name; + periph->clk.parent_names = &periph->parent; + periph->clk.num_parents = 1; + } + + periph->id = id; + periph->div = 0; + periph->regmap = regmap; + periph->auto_div = true; + periph->range = *range; + + ret = clk_register(&periph->clk); + if (ret) { + kfree(periph); + return ERR_PTR(ret); + } + + clk_sam9x5_peripheral_autodiv(periph); + + 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 new file mode 100644 index 0000000..e0af4fe --- /dev/null +++ b/drivers/clk/at91/clk-pll.c @@ -0,0 +1,516 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +#define PLL_STATUS_MASK(id) (1 << (1 + (id))) +#define PLL_REG(id) (AT91_CKGR_PLLAR + ((id) * 4)) +#define PLL_DIV_MASK 0xff +#define PLL_DIV_MAX PLL_DIV_MASK +#define PLL_DIV(reg) ((reg) & PLL_DIV_MASK) +#define PLL_MUL(reg, layout) (((reg) >> (layout)->mul_shift) & \ + (layout)->mul_mask) +#define PLL_MUL_MIN 2 +#define PLL_MUL_MASK(layout) ((layout)->mul_mask) +#define PLL_MUL_MAX(layout) (PLL_MUL_MASK(layout) + 1) +#define PLL_ICPR_SHIFT(id) ((id) * 16) +#define PLL_ICPR_MASK(id) (0xffff << PLL_ICPR_SHIFT(id)) +#define PLL_MAX_COUNT 0x3f +#define PLL_COUNT_SHIFT 8 +#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 { + struct clk clk; + struct regmap *regmap; + u8 id; + u8 div; + u8 range; + u16 mul; + const struct clk_pll_layout *layout; + const struct clk_pll_characteristics *characteristics; + const char *parent; +}; + +static inline bool clk_pll_ready(struct regmap *regmap, int id) +{ + unsigned int status; + + regmap_read(regmap, AT91_PMC_SR, &status); + + return status & PLL_STATUS_MASK(id) ? 1 : 0; +} + +static int clk_pll_enable(struct clk *clk) +{ + struct clk_pll *pll = to_clk_pll(clk); + struct regmap *regmap = pll->regmap; + const struct clk_pll_layout *layout = pll->layout; + const struct clk_pll_characteristics *characteristics = + pll->characteristics; + u8 id = pll->id; + u32 mask = PLL_STATUS_MASK(id); + int offset = PLL_REG(id); + u8 out = 0; + unsigned int pllr; + unsigned int status; + u8 div; + u16 mul; + + regmap_read(regmap, offset, &pllr); + div = PLL_DIV(pllr); + mul = PLL_MUL(pllr, layout); + + regmap_read(regmap, AT91_PMC_SR, &status); + if ((status & mask) && + (div == pll->div && mul == pll->mul)) + return 0; + + if (characteristics->out) + out = characteristics->out[pll->range]; + + if (characteristics->icpll) + regmap_write_bits(regmap, AT91_PMC_PLLICPR, PLL_ICPR_MASK(id), + characteristics->icpll[pll->range] << PLL_ICPR_SHIFT(id)); + + regmap_write_bits(regmap, offset, layout->pllr_mask, + pll->div | (PLL_MAX_COUNT << PLL_COUNT_SHIFT) | + (out << PLL_OUT_SHIFT) | + ((pll->mul & layout->mul_mask) << layout->mul_shift)); + + while (!clk_pll_ready(regmap, pll->id)) + barrier(); + + return 0; +} + +static int clk_pll_is_enabled(struct clk *clk) +{ + struct clk_pll *pll = to_clk_pll(clk); + + return clk_pll_ready(pll->regmap, pll->id); +} + +static void clk_pll_disable(struct clk *clk) +{ + struct clk_pll *pll = to_clk_pll(clk); + unsigned int mask = pll->layout->pllr_mask; + + regmap_write_bits(pll->regmap, PLL_REG(pll->id), mask, ~mask); +} + +static unsigned long clk_pll_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_pll *pll = to_clk_pll(clk); + unsigned int pllr; + u16 mul; + u8 div; + + regmap_read(pll->regmap, PLL_REG(pll->id), &pllr); + + div = PLL_DIV(pllr); + mul = PLL_MUL(pllr, pll->layout); + + if (!div || !mul) + return 0; + + return (parent_rate / div) * (mul + 1); +} + +static long clk_pll_get_best_div_mul(struct clk_pll *pll, unsigned long rate, + unsigned long parent_rate, + u32 *div, u32 *mul, + u32 *index) { + const struct clk_pll_layout *layout = pll->layout; + const struct clk_pll_characteristics *characteristics = + pll->characteristics; + unsigned long bestremainder = ULONG_MAX; + unsigned long maxdiv, mindiv, tmpdiv; + long bestrate = -ERANGE; + unsigned long bestdiv; + unsigned long bestmul; + int i = 0; + + /* Check if parent_rate is a valid input rate */ + if (parent_rate < characteristics->input.min) + return -ERANGE; + + /* + * Calculate minimum divider based on the minimum multiplier, the + * parent_rate and the requested rate. + * Should always be 2 according to the input and output characteristics + * of the PLL blocks. + */ + mindiv = (parent_rate * PLL_MUL_MIN) / rate; + if (!mindiv) + mindiv = 1; + + if (parent_rate > characteristics->input.max) { + tmpdiv = DIV_ROUND_UP(parent_rate, characteristics->input.max); + if (tmpdiv > PLL_DIV_MAX) + return -ERANGE; + + if (tmpdiv > mindiv) + mindiv = tmpdiv; + } + + /* + * Calculate the maximum divider which is limited by PLL register + * layout (limited by the MUL or DIV field size). + */ + maxdiv = DIV_ROUND_UP(parent_rate * PLL_MUL_MAX(layout), rate); + if (maxdiv > PLL_DIV_MAX) + maxdiv = PLL_DIV_MAX; + + /* + * Iterate over the acceptable divider values to find the best + * divider/multiplier pair (the one that generates the closest + * rate to the requested one). + */ + for (tmpdiv = mindiv; tmpdiv <= maxdiv; tmpdiv++) { + unsigned long remainder; + unsigned long tmprate; + unsigned long tmpmul; + + /* + * Calculate the multiplier associated with the current + * divider that provide the closest rate to the requested one. + */ + tmpmul = DIV_ROUND_CLOSEST(rate, parent_rate / tmpdiv); + tmprate = (parent_rate / tmpdiv) * tmpmul; + if (tmprate > rate) + remainder = tmprate - rate; + else + remainder = rate - tmprate; + + /* + * Compare the remainder with the best remainder found until + * now and elect a new best multiplier/divider pair if the + * current remainder is smaller than the best one. + */ + if (remainder < bestremainder) { + bestremainder = remainder; + bestdiv = tmpdiv; + bestmul = tmpmul; + bestrate = tmprate; + } + + /* + * We've found a perfect match! + * Stop searching now and use this multiplier/divider pair. + */ + if (!remainder) + break; + } + + /* We haven't found any multiplier/divider pair => return -ERANGE */ + if (bestrate < 0) + return bestrate; + + /* Check if bestrate is a valid output rate */ + for (i = 0; i < characteristics->num_output; i++) { + if (bestrate >= characteristics->output[i].min && + bestrate <= characteristics->output[i].max) + break; + } + + if (i >= characteristics->num_output) + return -ERANGE; + + if (div) + *div = bestdiv; + if (mul) + *mul = bestmul - 1; + if (index) + *index = i; + + return bestrate; +} + +static long clk_pll_round_rate(struct clk *clk, unsigned long rate, + unsigned long *parent_rate) +{ + struct clk_pll *pll = to_clk_pll(clk); + + return clk_pll_get_best_div_mul(pll, rate, *parent_rate, + NULL, NULL, NULL); +} + +static int clk_pll_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_pll *pll = to_clk_pll(clk); + long ret; + u32 div; + u32 mul; + u32 index; + + ret = clk_pll_get_best_div_mul(pll, rate, parent_rate, + &div, &mul, &index); + if (ret < 0) + return ret; + + pll->range = index; + pll->div = div; + pll->mul = mul; + + return 0; +} + +static const struct clk_ops pll_ops = { + .enable = clk_pll_enable, + .disable = clk_pll_disable, + .is_enabled = clk_pll_is_enabled, + .recalc_rate = clk_pll_recalc_rate, + .round_rate = clk_pll_round_rate, + .set_rate = clk_pll_set_rate, +}; + +static 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_pll *pll; + int offset = PLL_REG(id); + unsigned int pllr; + int ret; + + if (id > PLL_MAX_ID) + return ERR_PTR(-EINVAL); + + pll = xzalloc(sizeof(*pll)); + + pll->parent = parent_name; + pll->clk.name = name; + pll->clk.ops = &pll_ops; + pll->clk.parent_names = &pll->parent; + pll->clk.num_parents = 1; + + /* init.flags = CLK_SET_RATE_GATE; */ + + pll->id = id; + pll->layout = layout; + pll->characteristics = characteristics; + pll->regmap = regmap; + regmap_read(regmap, offset, &pllr); + pll->div = PLL_DIV(pllr); + pll->mul = PLL_MUL(pllr, layout); + + ret = clk_register(&pll->clk); + if (ret) { + kfree(pll); + return ERR_PTR(ret); + } + + return &pll->clk; +} + + +static 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 = { + .pllr_mask = 0xFFFFFF, + .mul_shift = 16, + .mul_mask = 0xFF, +}; + +static 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 = { + .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 new file mode 100644 index 0000000..917108e --- /dev/null +++ b/drivers/clk/at91/clk-plldiv.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +#define to_clk_plldiv(hw) container_of(clk, struct clk_plldiv, clk) + +struct clk_plldiv { + struct clk clk; + struct regmap *regmap; + const char *parent; +}; + +static unsigned long clk_plldiv_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_plldiv *plldiv = to_clk_plldiv(clk); + unsigned int mckr; + + regmap_read(plldiv->regmap, AT91_PMC_MCKR, &mckr); + + if (mckr & AT91_PMC_PLLADIV2) + return parent_rate / 2; + + return parent_rate; +} + +static long clk_plldiv_round_rate(struct clk *clk, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long div; + + if (rate > *parent_rate) + return *parent_rate; + div = *parent_rate / 2; + if (rate < div) + return div; + + if (rate - div < *parent_rate - rate) + return div; + + return *parent_rate; +} + +static int clk_plldiv_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_plldiv *plldiv = to_clk_plldiv(clk); + + if ((parent_rate != rate) && (parent_rate / 2 != rate)) + return -EINVAL; + + regmap_write_bits(plldiv->regmap, AT91_PMC_MCKR, AT91_PMC_PLLADIV2, + parent_rate != rate ? AT91_PMC_PLLADIV2 : 0); + + return 0; +} + +static const struct clk_ops plldiv_ops = { + .recalc_rate = clk_plldiv_recalc_rate, + .round_rate = clk_plldiv_round_rate, + .set_rate = clk_plldiv_set_rate, +}; + +static struct clk * +at91_clk_register_plldiv(struct regmap *regmap, const char *name, + const char *parent_name) +{ + int ret; + struct clk_plldiv *plldiv; + + plldiv = xzalloc(sizeof(*plldiv)); + + plldiv->clk.name = name; + plldiv->clk.ops = &plldiv_ops; + + if (parent_name) { + plldiv->parent = parent_name; + plldiv->clk.parent_names = &plldiv->parent; + plldiv->clk.num_parents = 1; + } + + /* init.flags = CLK_SET_RATE_GATE; */ + + plldiv->regmap = regmap; + + ret = clk_register(&plldiv->clk); + if (ret) { + kfree(plldiv); + return ERR_PTR(ret); + } + + 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 new file mode 100644 index 0000000..ddb18c0 --- /dev/null +++ b/drivers/clk/at91/clk-programmable.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +#define PROG_SOURCE_MAX 5 +#define PROG_ID_MAX 7 + +#define PROG_STATUS_MASK(id) (1 << ((id) + 8)) +#define PROG_PRES_MASK 0x7 +#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; + u8 id; + const struct clk_programmable_layout *layout; + const char *parent_names[PROG_SOURCE_MAX]; +}; + +#define to_clk_programmable(clk) container_of(clk, struct clk_programmable, clk) + +static unsigned long clk_programmable_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_programmable *prog = to_clk_programmable(clk); + unsigned int pckr; + + regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr); + + return parent_rate >> PROG_PRES(prog->layout, pckr); +} + +static int clk_programmable_set_parent(struct clk *clk, u8 index) +{ + struct clk_programmable *prog = to_clk_programmable(clk); + const struct clk_programmable_layout *layout = prog->layout; + unsigned int mask = layout->css_mask; + unsigned int pckr = index; + + if (layout->have_slck_mck) + mask |= AT91_PMC_CSSMCK_MCK; + + if (index > layout->css_mask) { + if (index > PROG_MAX_RM9200_CSS && !layout->have_slck_mck) + return -EINVAL; + + pckr |= AT91_PMC_CSSMCK_MCK; + } + + regmap_write_bits(prog->regmap, AT91_PMC_PCKR(prog->id), mask, pckr); + + return 0; +} + +static int clk_programmable_get_parent(struct clk *clk) +{ + struct clk_programmable *prog = to_clk_programmable(clk); + const struct clk_programmable_layout *layout = prog->layout; + unsigned int pckr; + u8 ret; + + regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr); + + ret = pckr & layout->css_mask; + + if (layout->have_slck_mck && (pckr & AT91_PMC_CSSMCK_MCK) && !ret) + ret = PROG_MAX_RM9200_CSS + 1; + + return ret; +} + +static int clk_programmable_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_programmable *prog = to_clk_programmable(clk); + const struct clk_programmable_layout *layout = prog->layout; + unsigned long div = parent_rate / rate; + unsigned int pckr; + int shift = 0; + + regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr); + + if (!div) + return -EINVAL; + + shift = fls(div) - 1; + + if (div != (1 << shift)) + return -EINVAL; + + if (shift >= PROG_PRES_MASK) + return -EINVAL; + + regmap_write_bits(prog->regmap, AT91_PMC_PCKR(prog->id), + PROG_PRES_MASK << layout->pres_shift, + shift << layout->pres_shift); + + return 0; +} + +static const struct clk_ops programmable_ops = { + .recalc_rate = clk_programmable_recalc_rate, + .get_parent = clk_programmable_get_parent, + .set_parent = clk_programmable_set_parent, + .set_rate = clk_programmable_set_rate, +}; + +static 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_programmable *prog; + int ret; + + if (id > PROG_ID_MAX) + return ERR_PTR(-EINVAL); + + prog = kzalloc(sizeof(*prog), GFP_KERNEL); + if (!prog) + return ERR_PTR(-ENOMEM); + + prog->clk.name = name; + prog->clk.ops = &programmable_ops; + memcpy(prog->parent_names, parent_names, + num_parents * sizeof(prog->parent_names[0])); + prog->clk.parent_names = &prog->parent_names[0]; + prog->clk.num_parents = num_parents; + /* init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; */ + + prog->id = id; + prog->layout = layout; + prog->regmap = regmap; + + ret = clk_register(&prog->clk); + if (ret) { + kfree(prog); + return ERR_PTR(ret); + } + + return &prog->clk; +} + +static 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 = { + .pres_shift = 2, + .css_mask = 0x3, + .have_slck_mck = 1, +}; + +static 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 new file mode 100644 index 0000000..d4981e7 --- /dev/null +++ b/drivers/clk/at91/clk-slow.c @@ -0,0 +1,108 @@ +/* + * drivers/clk/at91/clk-slow.c + * + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +struct clk_sam9260_slow { + struct clk clk; + struct regmap *regmap; + const char *parent_names[2]; +}; + +#define to_clk_sam9260_slow(clk) container_of(clk, struct clk_sam9260_slow, clk) + +static int clk_sam9260_slow_get_parent(struct clk *clk) +{ + struct clk_sam9260_slow *slowck = to_clk_sam9260_slow(clk); + unsigned int status; + + regmap_read(slowck->regmap, AT91_PMC_SR, &status); + + return status & AT91_PMC_OSCSEL ? 1 : 0; +} + +static const struct clk_ops sam9260_slow_ops = { + .get_parent = clk_sam9260_slow_get_parent, +}; + +static struct clk * __init +at91_clk_register_sam9260_slow(struct regmap *regmap, + const char *name, + const char **parent_names, + int num_parents) +{ + struct clk_sam9260_slow *slowck; + int ret; + + if (!name) + return ERR_PTR(-EINVAL); + + if (!parent_names || !num_parents) + return ERR_PTR(-EINVAL); + + slowck = xzalloc(sizeof(*slowck)); + slowck->clk.name = name; + slowck->clk.ops = &sam9260_slow_ops; + memcpy(slowck->parent_names, parent_names, + num_parents * sizeof(slowck->parent_names[0])); + slowck->clk.parent_names = slowck->parent_names; + slowck->clk.num_parents = num_parents; + slowck->regmap = regmap; + + ret = clk_register(&slowck->clk); + if (ret) { + kfree(slowck); + return ERR_PTR(ret); + } + + 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 new file mode 100644 index 0000000..65c53ef --- /dev/null +++ b/drivers/clk/at91/clk-smd.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +#define SMD_SOURCE_MAX 2 + +#define SMD_DIV_SHIFT 8 +#define SMD_MAX_DIV 0xf + +struct at91sam9x5_clk_smd { + struct clk clk; + struct regmap *regmap; + const char *parent_names[SMD_SOURCE_MAX]; +}; + +#define to_at91sam9x5_clk_smd(clk) \ + container_of(clk, struct at91sam9x5_clk_smd, clk) + +static unsigned long at91sam9x5_clk_smd_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(clk); + unsigned int smdr; + u8 smddiv; + + regmap_read(smd->regmap, AT91_PMC_SMD, &smdr); + smddiv = (smdr & AT91_PMC_SMD_DIV) >> SMD_DIV_SHIFT; + + return parent_rate / (smddiv + 1); +} + +static long at91sam9x5_clk_smd_round_rate(struct clk *clk, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long div; + unsigned long bestrate; + unsigned long tmp; + + if (rate >= *parent_rate) + return *parent_rate; + + div = *parent_rate / rate; + if (div > SMD_MAX_DIV) + return *parent_rate / (SMD_MAX_DIV + 1); + + bestrate = *parent_rate / div; + tmp = *parent_rate / (div + 1); + if (bestrate - rate > rate - tmp) + bestrate = tmp; + + return bestrate; +} + +static int at91sam9x5_clk_smd_set_parent(struct clk *clk, u8 index) +{ + struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(clk); + + if (index > 1) + return -EINVAL; + + regmap_write_bits(smd->regmap, AT91_PMC_SMD, AT91_PMC_SMDS, + index ? AT91_PMC_SMDS : 0); + + return 0; +} + +static int at91sam9x5_clk_smd_get_parent(struct clk *clk) +{ + struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(clk); + unsigned int smdr; + + regmap_read(smd->regmap, AT91_PMC_SMD, &smdr); + + return smdr & AT91_PMC_SMDS; +} + +static int at91sam9x5_clk_smd_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(clk); + unsigned long div = parent_rate / rate; + + if (parent_rate % rate || div < 1 || div > (SMD_MAX_DIV + 1)) + return -EINVAL; + + regmap_write_bits(smd->regmap, AT91_PMC_SMD, AT91_PMC_SMD_DIV, + (div - 1) << SMD_DIV_SHIFT); + + return 0; +} + +static const struct clk_ops at91sam9x5_smd_ops = { + .recalc_rate = at91sam9x5_clk_smd_recalc_rate, + .round_rate = at91sam9x5_clk_smd_round_rate, + .get_parent = at91sam9x5_clk_smd_get_parent, + .set_parent = at91sam9x5_clk_smd_set_parent, + .set_rate = at91sam9x5_clk_smd_set_rate, +}; + +static struct clk * +at91sam9x5_clk_register_smd(struct regmap *regmap, const char *name, + const char **parent_names, u8 num_parents) +{ + struct at91sam9x5_clk_smd *smd; + int ret; + + smd = xzalloc(sizeof(*smd)); + smd->clk.name = name; + smd->clk.ops = &at91sam9x5_smd_ops; + memcpy(smd->parent_names, parent_names, + num_parents * sizeof(smd->parent_names[0])); + smd->clk.parent_names = smd->parent_names; + smd->clk.num_parents = num_parents; + /* init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; */ + smd->regmap = regmap; + + ret = clk_register(&smd->clk); + if (ret) { + kfree(smd); + return ERR_PTR(ret); + } + + 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 new file mode 100644 index 0000000..021930e --- /dev/null +++ b/drivers/clk/at91/clk-system.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +#define SYSTEM_MAX_ID 31 + +#define SYSTEM_MAX_NAME_SZ 32 + +#define to_clk_system(clk) container_of(clk, struct clk_system, clk) +struct clk_system { + struct clk clk; + struct regmap *regmap; + u8 id; + const char *parent_name; +}; + +static inline int is_pck(int id) +{ + return (id >= 8) && (id <= 15); +} + +static inline bool clk_system_ready(struct regmap *regmap, int id) +{ + unsigned int status; + + regmap_read(regmap, AT91_PMC_SR, &status); + + return status & (1 << id) ? 1 : 0; +} + +static int clk_system_enable(struct clk *clk) +{ + struct clk_system *sys = to_clk_system(clk); + + regmap_write(sys->regmap, AT91_PMC_SCER, 1 << sys->id); + + if (!is_pck(sys->id)) + return 0; + + while (!clk_system_ready(sys->regmap, sys->id)) + barrier(); + + return 0; +} + +static void clk_system_disable(struct clk *clk) +{ + struct clk_system *sys = to_clk_system(clk); + + regmap_write(sys->regmap, AT91_PMC_SCDR, 1 << sys->id); +} + +static int clk_system_is_enabled(struct clk *clk) +{ + struct clk_system *sys = to_clk_system(clk); + unsigned int status; + + regmap_read(sys->regmap, AT91_PMC_SCSR, &status); + + if (!(status & (1 << sys->id))) + return 0; + + if (!is_pck(sys->id)) + return 1; + + regmap_read(sys->regmap, AT91_PMC_SR, &status); + + return status & (1 << sys->id) ? 1 : 0; +} + +static const struct clk_ops system_ops = { + .enable = clk_system_enable, + .disable = clk_system_disable, + .is_enabled = clk_system_is_enabled, +}; + +static struct clk * +at91_clk_register_system(struct regmap *regmap, const char *name, + const char *parent_name, u8 id) +{ + struct clk_system *sys; + int ret; + + if (!parent_name || id > SYSTEM_MAX_ID) + return ERR_PTR(-EINVAL); + + sys = xzalloc(sizeof(*sys)); + sys->clk.name = name; + sys->clk.ops = &system_ops; + sys->parent_name = parent_name; + sys->clk.parent_names = &sys->parent_name; + sys->clk.num_parents = 1; + /* init.flags = CLK_SET_RATE_PARENT; */ + sys->id = id; + sys->regmap = regmap; + + ret = clk_register(&sys->clk); + if (ret) { + kfree(sys); + return ERR_PTR(ret); + } + + 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 new file mode 100644 index 0000000..99ba671 --- /dev/null +++ b/drivers/clk/at91/clk-usb.c @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +#define USB_SOURCE_MAX 2 + +#define SAM9X5_USB_DIV_SHIFT 8 +#define SAM9X5_USB_MAX_DIV 0xf + +#define RM9200_USB_DIV_SHIFT 28 +#define RM9200_USB_DIV_TAB_SIZE 4 + +struct at91sam9x5_clk_usb { + struct clk clk; + struct regmap *regmap; + const char *parent_names[USB_SOURCE_MAX]; +}; + +#define to_at91sam9x5_clk_usb(clk) \ + container_of(clk, struct at91sam9x5_clk_usb, clk) + +struct at91rm9200_clk_usb { + struct clk clk; + struct regmap *regmap; + u32 divisors[4]; + const char *parent_name; +}; + +#define to_at91rm9200_clk_usb(clk) \ + container_of(clk, struct at91rm9200_clk_usb, clk) + +static unsigned long at91sam9x5_clk_usb_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk); + unsigned int usbr; + u8 usbdiv; + + regmap_read(usb->regmap, AT91_PMC_USB, &usbr); + usbdiv = (usbr & AT91_PMC_OHCIUSBDIV) >> SAM9X5_USB_DIV_SHIFT; + + return DIV_ROUND_CLOSEST(parent_rate, (usbdiv + 1)); +} + +static int at91sam9x5_clk_usb_set_parent(struct clk *clk, u8 index) +{ + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk); + + if (index > 1) + return -EINVAL; + + regmap_write_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_USBS, + index ? AT91_PMC_USBS : 0); + + return 0; +} + +static int at91sam9x5_clk_usb_get_parent(struct clk *clk) +{ + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk); + unsigned int usbr; + + regmap_read(usb->regmap, AT91_PMC_USB, &usbr); + + return usbr & AT91_PMC_USBS; +} + +static int at91sam9x5_clk_usb_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk); + unsigned long div; + + if (!rate) + return -EINVAL; + + div = DIV_ROUND_CLOSEST(parent_rate, rate); + if (div > SAM9X5_USB_MAX_DIV + 1 || !div) + return -EINVAL; + + regmap_write_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_OHCIUSBDIV, + (div - 1) << SAM9X5_USB_DIV_SHIFT); + + return 0; +} + +static const struct clk_ops at91sam9x5_usb_ops = { + .recalc_rate = at91sam9x5_clk_usb_recalc_rate, + .get_parent = at91sam9x5_clk_usb_get_parent, + .set_parent = at91sam9x5_clk_usb_set_parent, + .set_rate = at91sam9x5_clk_usb_set_rate, +}; + +static int at91sam9n12_clk_usb_enable(struct clk *clk) +{ + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk); + + regmap_write_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_USBS, + AT91_PMC_USBS); + + return 0; +} + +static void at91sam9n12_clk_usb_disable(struct clk *clk) +{ + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk); + + regmap_write_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_USBS, 0); +} + +static int at91sam9n12_clk_usb_is_enabled(struct clk *clk) +{ + struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(clk); + unsigned int usbr; + + regmap_read(usb->regmap, AT91_PMC_USB, &usbr); + + return usbr & AT91_PMC_USBS; +} + +static const struct clk_ops at91sam9n12_usb_ops = { + .enable = at91sam9n12_clk_usb_enable, + .disable = at91sam9n12_clk_usb_disable, + .is_enabled = at91sam9n12_clk_usb_is_enabled, + .recalc_rate = at91sam9x5_clk_usb_recalc_rate, + .set_rate = at91sam9x5_clk_usb_set_rate, +}; + +static struct clk * +at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name, + const char **parent_names, u8 num_parents) +{ + struct at91sam9x5_clk_usb *usb; + int ret; + + usb = kzalloc(sizeof(*usb), GFP_KERNEL); + usb->clk.name = name; + usb->clk.ops = &at91sam9x5_usb_ops; + memcpy(usb->parent_names, parent_names, + num_parents * sizeof(usb->parent_names[0])); + usb->clk.parent_names = usb->parent_names; + usb->clk.num_parents = num_parents; + usb->clk.flags = CLK_SET_RATE_PARENT; + /* init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE | */ + /* CLK_SET_RATE_PARENT; */ + usb->regmap = regmap; + + ret = clk_register(&usb->clk); + if (ret) { + kfree(usb); + return ERR_PTR(ret); + } + + return &usb->clk; +} + +static struct clk * +at91sam9n12_clk_register_usb(struct regmap *regmap, const char *name, + const char *parent_name) +{ + struct at91sam9x5_clk_usb *usb; + int ret; + + usb = xzalloc(sizeof(*usb)); + usb->clk.name = name; + usb->clk.ops = &at91sam9n12_usb_ops; + usb->parent_names[0] = parent_name; + usb->clk.parent_names = &usb->parent_names[0]; + usb->clk.num_parents = 1; + /* init.flags = CLK_SET_RATE_GATE | CLK_SET_RATE_PARENT; */ + usb->regmap = regmap; + + ret = clk_register(&usb->clk); + if (ret) { + kfree(usb); + return ERR_PTR(ret); + } + + return &usb->clk; +} + +static unsigned long at91rm9200_clk_usb_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(clk); + unsigned int pllbr; + u8 usbdiv; + + regmap_read(usb->regmap, AT91_CKGR_PLLBR, &pllbr); + + usbdiv = (pllbr & AT91_PMC_USBDIV) >> RM9200_USB_DIV_SHIFT; + if (usb->divisors[usbdiv]) + return parent_rate / usb->divisors[usbdiv]; + + return 0; +} + +static long at91rm9200_clk_usb_round_rate(struct clk *clk, unsigned long rate, + unsigned long *parent_rate) +{ + struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(clk); + struct clk *parent = clk_get_parent(clk); + unsigned long bestrate = 0; + int bestdiff = -1; + unsigned long tmprate; + int tmpdiff; + int i = 0; + + for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) { + unsigned long tmp_parent_rate; + + if (!usb->divisors[i]) + continue; + + tmp_parent_rate = rate * usb->divisors[i]; + tmp_parent_rate = clk_round_rate(parent, tmp_parent_rate); + tmprate = DIV_ROUND_CLOSEST(tmp_parent_rate, usb->divisors[i]); + if (tmprate < rate) + tmpdiff = rate - tmprate; + else + tmpdiff = tmprate - rate; + + if (bestdiff < 0 || bestdiff > tmpdiff) { + bestrate = tmprate; + bestdiff = tmpdiff; + *parent_rate = tmp_parent_rate; + } + + if (!bestdiff) + break; + } + + return bestrate; +} + +static int at91rm9200_clk_usb_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + int i; + struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(clk); + unsigned long div; + + if (!rate) + return -EINVAL; + + div = DIV_ROUND_CLOSEST(parent_rate, rate); + + for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) { + if (usb->divisors[i] == div) { + regmap_write_bits(usb->regmap, AT91_CKGR_PLLBR, + AT91_PMC_USBDIV, + i << RM9200_USB_DIV_SHIFT); + + return 0; + } + } + + return -EINVAL; +} + +static const struct clk_ops at91rm9200_usb_ops = { + .recalc_rate = at91rm9200_clk_usb_recalc_rate, + .round_rate = at91rm9200_clk_usb_round_rate, + .set_rate = at91rm9200_clk_usb_set_rate, +}; + +static struct clk * +at91rm9200_clk_register_usb(struct regmap *regmap, const char *name, + const char *parent_name, const u32 *divisors) +{ + struct at91rm9200_clk_usb *usb; + int ret; + + usb = xzalloc(sizeof(*usb)); + usb->clk.name = name; + usb->clk.ops = &at91rm9200_usb_ops; + usb->parent_name = parent_name; + usb->clk.parent_names = &usb->parent_name; + usb->clk.num_parents = 1; + /* init.flags = CLK_SET_RATE_PARENT; */ + + usb->regmap = regmap; + memcpy(usb->divisors, divisors, sizeof(usb->divisors)); + + ret = clk_register(&usb->clk); + if (ret) { + kfree(usb); + return ERR_PTR(ret); + } + + 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 new file mode 100644 index 0000000..6a1c5e6 --- /dev/null +++ b/drivers/clk/at91/clk-utmi.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +#define UTMI_FIXED_MUL 40 + +struct clk_utmi { + struct clk clk; + struct regmap *regmap; + const char *parent; +}; + +#define to_clk_utmi(clk) container_of(clk, struct clk_utmi, clk) + +static inline bool clk_utmi_ready(struct regmap *regmap) +{ + unsigned int status; + + regmap_read(regmap, AT91_PMC_SR, &status); + + return status & AT91_PMC_LOCKU; +} + +static int clk_utmi_enable(struct clk *clk) +{ + struct clk_utmi *utmi = to_clk_utmi(clk); + unsigned int uckr = AT91_PMC_UPLLEN | AT91_PMC_UPLLCOUNT | + AT91_PMC_BIASEN; + + regmap_write_bits(utmi->regmap, AT91_CKGR_UCKR, uckr, uckr); + + while (!clk_utmi_ready(utmi->regmap)) + barrier(); + + return 0; +} + +static int clk_utmi_is_enabled(struct clk *clk) +{ + struct clk_utmi *utmi = to_clk_utmi(clk); + + return clk_utmi_ready(utmi->regmap); +} + +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); +} + +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; +} + +static const struct clk_ops utmi_ops = { + .enable = clk_utmi_enable, + .disable = clk_utmi_disable, + .is_enabled = clk_utmi_is_enabled, + .recalc_rate = clk_utmi_recalc_rate, +}; + +static struct clk * __init +at91_clk_register_utmi(struct regmap *regmap, + const char *name, const char *parent_name) +{ + int ret; + struct clk_utmi *utmi; + + utmi = xzalloc(sizeof(*utmi)); + + utmi->clk.name = name; + utmi->clk.ops = &utmi_ops; + + if (parent_name) { + utmi->parent = parent_name; + utmi->clk.parent_names = &utmi->parent; + utmi->clk.num_parents = 1; + } + + /* utmi->clk.flags = CLK_SET_RATE_GATE; */ + + utmi->regmap = regmap; + + ret = clk_register(&utmi->clk); + if (ret) { + kfree(utmi); + return ERR_PTR(ret); + } + + 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/pmc.c b/drivers/clk/at91/pmc.c new file mode 100644 index 0000000..d156d50 --- /dev/null +++ b/drivers/clk/at91/pmc.c @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "pmc.h" + +int of_at91_get_clk_range(struct device_node *np, const char *propname, + struct clk_range *range) +{ + u32 min, max; + int ret; + + ret = of_property_read_u32_index(np, propname, 0, &min); + if (ret) + return ret; + + ret = of_property_read_u32_index(np, propname, 1, &max); + if (ret) + return ret; + + if (range) { + range->min = min; + range->max = max; + } + + return 0; +} +EXPORT_SYMBOL_GPL(of_at91_get_clk_range); diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h new file mode 100644 index 0000000..c6c14a7 --- /dev/null +++ b/drivers/clk/at91/pmc.h @@ -0,0 +1,27 @@ +/* + * drivers/clk/at91/pmc.h + * + * Copyright (C) 2013 Boris BREZILLON + * + * 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. + */ + +#ifndef __PMC_H_ +#define __PMC_H_ + +#include + +struct clk_range { + unsigned long min; + unsigned long max; +}; + +#define CLK_RANGE(MIN, MAX) {.min = MIN, .max = MAX,} + +int of_at91_get_clk_range(struct device_node *np, const char *propname, + struct clk_range *range); + +#endif /* __PMC_H_ */ diff --git a/drivers/clk/at91/sckc.c b/drivers/clk/at91/sckc.c new file mode 100644 index 0000000..bac2899 --- /dev/null +++ b/drivers/clk/at91/sckc.c @@ -0,0 +1,485 @@ +/* + * drivers/clk/at91/sckc.c + * + * Copyright (C) 2013 Boris BREZILLON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +#define SLOW_CLOCK_FREQ 32768 +#define SLOWCK_SW_CYCLES 5 +#define SLOWCK_SW_TIME_USEC ((SLOWCK_SW_CYCLES * SECOND) / \ + SLOW_CLOCK_FREQ) + +#define AT91_SCKC_CR 0x00 +#define AT91_SCKC_RCEN (1 << 0) +#define AT91_SCKC_OSC32EN (1 << 1) +#define AT91_SCKC_OSC32BYP (1 << 2) +#define AT91_SCKC_OSCSEL (1 << 3) + +struct clk_slow_osc { + struct clk clk; + void __iomem *sckcr; + unsigned long startup_usec; + const char *parent_name; +}; + +#define to_clk_slow_osc(clk) container_of(clk, struct clk_slow_osc, clk) + +struct clk_sama5d4_slow_osc { + struct clk clk; + void __iomem *sckcr; + unsigned long startup_usec; + bool prepared; + const char *parent_name; +}; + +#define to_clk_sama5d4_slow_osc(clk) container_of(clk, struct clk_sama5d4_slow_osc, clk) + +struct clk_slow_rc_osc { + struct clk clk; + void __iomem *sckcr; + unsigned long frequency; + unsigned long startup_usec; + const char *parent_name; +}; + +#define to_clk_slow_rc_osc(clk) container_of(clk, struct clk_slow_rc_osc, clk) + +struct clk_sam9x5_slow { + struct clk clk; + void __iomem *sckcr; + u8 parent; + const char *parent_names[2]; +}; + +#define to_clk_sam9x5_slow(clk) container_of(clk, struct clk_sam9x5_slow, clk) + +static int clk_slow_osc_enable(struct clk *clk) +{ + struct clk_slow_osc *osc = to_clk_slow_osc(clk); + void __iomem *sckcr = osc->sckcr; + u32 tmp = readl(sckcr); + + if (tmp & (AT91_SCKC_OSC32BYP | AT91_SCKC_OSC32EN)) + return 0; + + writel(tmp | AT91_SCKC_OSC32EN, sckcr); + + udelay(osc->startup_usec); + + return 0; +} + +static void clk_slow_osc_disable(struct clk *clk) +{ + struct clk_slow_osc *osc = to_clk_slow_osc(clk); + void __iomem *sckcr = osc->sckcr; + u32 tmp = readl(sckcr); + + if (tmp & AT91_SCKC_OSC32BYP) + return; + + writel(tmp & ~AT91_SCKC_OSC32EN, sckcr); +} + +static int clk_slow_osc_is_enabled(struct clk *clk) +{ + struct clk_slow_osc *osc = to_clk_slow_osc(clk); + void __iomem *sckcr = osc->sckcr; + u32 tmp = readl(sckcr); + + if (tmp & AT91_SCKC_OSC32BYP) + return 1; + + return !!(tmp & AT91_SCKC_OSC32EN); +} + +static const struct clk_ops slow_osc_ops = { + .enable = clk_slow_osc_enable, + .disable = clk_slow_osc_disable, + .is_enabled = clk_slow_osc_is_enabled, +}; + +static struct clk * +at91_clk_register_slow_osc(void __iomem *sckcr, + const char *name, + const char *parent_name, + unsigned long startup, + bool bypass) +{ + int ret; + struct clk_slow_osc *osc; + + if (!sckcr || !name || !parent_name) + return ERR_PTR(-EINVAL); + + osc = xzalloc(sizeof(*osc)); + + osc->clk.name = name; + osc->clk.ops = &slow_osc_ops; + osc->parent_name = parent_name; + osc->clk.parent_names = &osc->parent_name; + osc->clk.num_parents = 1; + + osc->sckcr = sckcr; + osc->startup_usec = startup; + + if (bypass) + writel((readl(sckcr) & ~AT91_SCKC_OSC32EN) | AT91_SCKC_OSC32BYP, + sckcr); + + ret = clk_register(&osc->clk); + if (ret) { + kfree(osc); + return ERR_PTR(ret); + } + + return &osc->clk; +} + +static void +of_at91sam9x5_clk_slow_osc_setup(struct device_node *np, void __iomem *sckcr) +{ + struct clk *clk; + const char *parent_name; + const char *name = np->name; + u32 startup; + bool bypass; + + parent_name = of_clk_get_parent_name(np, 0); + of_property_read_string(np, "clock-output-names", &name); + of_property_read_u32(np, "atmel,startup-time-usec", &startup); + bypass = of_property_read_bool(np, "atmel,osc-bypass"); + + clk = at91_clk_register_slow_osc(sckcr, name, parent_name, startup, + bypass); + if (IS_ERR(clk)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, clk); +} + +static unsigned long clk_slow_rc_osc_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(clk); + + return osc->frequency; +} + +static int clk_slow_rc_osc_enable(struct clk *clk) +{ + struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(clk); + void __iomem *sckcr = osc->sckcr; + + writel(readl(sckcr) | AT91_SCKC_RCEN, sckcr); + + udelay(osc->startup_usec); + + return 0; +} + +static void clk_slow_rc_osc_disable(struct clk *clk) +{ + struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(clk); + void __iomem *sckcr = osc->sckcr; + + writel(readl(sckcr) & ~AT91_SCKC_RCEN, sckcr); +} + +static int clk_slow_rc_osc_is_enabled(struct clk *clk) +{ + struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(clk); + + return !!(readl(osc->sckcr) & AT91_SCKC_RCEN); +} + +static const struct clk_ops slow_rc_osc_ops = { + .enable = clk_slow_rc_osc_enable, + .disable = clk_slow_rc_osc_disable, + .is_enabled = clk_slow_rc_osc_is_enabled, + .recalc_rate = clk_slow_rc_osc_recalc_rate, +}; + +static struct clk * +at91_clk_register_slow_rc_osc(void __iomem *sckcr, + const char *name, + unsigned long frequency, + unsigned long startup) +{ + struct clk_slow_rc_osc *osc; + int ret; + + if (!sckcr || !name) + return ERR_PTR(-EINVAL); + + osc = xzalloc(sizeof(*osc)); + osc->clk.name = name; + osc->clk.ops = &slow_rc_osc_ops; + osc->clk.parent_names = NULL; + osc->clk.num_parents = 0; + /* init.flags = CLK_IGNORE_UNUSED; */ + + osc->sckcr = sckcr; + osc->frequency = frequency; + osc->startup_usec = startup; + + ret = clk_register(&osc->clk); + if (ret) { + kfree(osc); + return ERR_PTR(ret); + } + + return &osc->clk; +} + +static void +of_at91sam9x5_clk_slow_rc_osc_setup(struct device_node *np, void __iomem *sckcr) +{ + struct clk *clk; + u32 frequency = 0; + u32 startup = 0; + const char *name = np->name; + + of_property_read_string(np, "clock-output-names", &name); + of_property_read_u32(np, "clock-frequency", &frequency); + of_property_read_u32(np, "atmel,startup-time-usec", &startup); + + clk = at91_clk_register_slow_rc_osc(sckcr, name, frequency, startup); + if (IS_ERR(clk)) + return; + + of_clk_add_provider(np, of_clk_src_simple_get, clk); +} + +static int clk_sam9x5_slow_set_parent(struct clk *clk, u8 index) +{ + struct clk_sam9x5_slow *slowck = to_clk_sam9x5_slow(clk); + void __iomem *sckcr = slowck->sckcr; + u32 tmp; + + if (index > 1) + return -EINVAL; + + tmp = readl(sckcr); + + if ((!index && !(tmp & AT91_SCKC_OSCSEL)) || + (index && (tmp & AT91_SCKC_OSCSEL))) + return 0; + + if (index) + tmp |= AT91_SCKC_OSCSEL; + else + tmp &= ~AT91_SCKC_OSCSEL; + + writel(tmp, sckcr); + + udelay(SLOWCK_SW_TIME_USEC); + + return 0; +} + +static int clk_sam9x5_slow_get_parent(struct clk *clk) +{ + struct clk_sam9x5_slow *slowck = to_clk_sam9x5_slow(clk); + + return !!(readl(slowck->sckcr) & AT91_SCKC_OSCSEL); +} + +static const struct clk_ops sam9x5_slow_ops = { + .set_parent = clk_sam9x5_slow_set_parent, + .get_parent = clk_sam9x5_slow_get_parent, +}; + +static struct clk * +at91_clk_register_sam9x5_slow(void __iomem *sckcr, + const char *name, + const char **parent_names, + int num_parents) +{ + struct clk_sam9x5_slow *slowck; + int ret; + + if (!sckcr || !name || !parent_names || !num_parents) + return ERR_PTR(-EINVAL); + + slowck = xzalloc(sizeof(*slowck)); + slowck->clk.name = name; + slowck->clk.ops = &sam9x5_slow_ops; + + memcpy(slowck->parent_names, parent_names, + num_parents * sizeof(slowck->parent_names[0])); + slowck->clk.parent_names = slowck->parent_names; + slowck->clk.num_parents = num_parents; + slowck->sckcr = sckcr; + slowck->parent = !!(readl(sckcr) & AT91_SCKC_OSCSEL); + + ret = clk_register(&slowck->clk); + if (ret) { + kfree(slowck); + return ERR_PTR(ret); + } + + return &slowck->clk; +} + +static int +of_at91sam9x5_clk_slow_setup(struct device_node *np, void __iomem *sckcr) +{ + struct clk *clk; + const char *parent_names[2]; + unsigned int num_parents; + const char *name = np->name; + + 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); + + of_property_read_string(np, "clock-output-names", &name); + + clk = at91_clk_register_sam9x5_slow(sckcr, 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); +} + +static const struct of_device_id sckc_clk_ids[] = { + /* Slow clock */ + { + .compatible = "atmel,at91sam9x5-clk-slow-osc", + .data = of_at91sam9x5_clk_slow_osc_setup, + }, + { + .compatible = "atmel,at91sam9x5-clk-slow-rc-osc", + .data = of_at91sam9x5_clk_slow_rc_osc_setup, + }, + { + .compatible = "atmel,at91sam9x5-clk-slow", + .data = of_at91sam9x5_clk_slow_setup, + }, + { /*sentinel*/ } +}; + +static int of_at91sam9x5_sckc_setup(struct device_node *np) +{ + struct device_node *childnp; + void (*clk_setup)(struct device_node *, void __iomem *); + const struct of_device_id *clk_id; + void __iomem *regbase = of_iomap(np, 0); + + if (!regbase) + return -ENOMEM; + + for_each_child_of_node(np, childnp) { + clk_id = of_match_node(sckc_clk_ids, childnp); + if (!clk_id) + continue; + clk_setup = clk_id->data; + clk_setup(childnp, regbase); + } + + return 0; +} +CLK_OF_DECLARE(at91sam9x5_clk_sckc, "atmel,at91sam9x5-sckc", + of_at91sam9x5_sckc_setup); + +static int clk_sama5d4_slow_osc_enable(struct clk *clk) +{ + struct clk_sama5d4_slow_osc *osc = to_clk_sama5d4_slow_osc(clk); + + if (osc->prepared) + return 0; + + /* + * Assume that if it has already been selected (for example by the + * bootloader), enough time has aready passed. + */ + if ((readl(osc->sckcr) & AT91_SCKC_OSCSEL)) { + osc->prepared = true; + return 0; + } + + udelay(osc->startup_usec); + osc->prepared = true; + + return 0; +} + +static int clk_sama5d4_slow_osc_is_enabled(struct clk *clk) +{ + struct clk_sama5d4_slow_osc *osc = to_clk_sama5d4_slow_osc(clk); + + return osc->prepared; +} + +static const struct clk_ops sama5d4_slow_osc_ops = { + .enable = clk_sama5d4_slow_osc_enable, + .is_enabled = clk_sama5d4_slow_osc_is_enabled, +}; + +static int of_sama5d4_sckc_setup(struct device_node *np) +{ + void __iomem *regbase = of_iomap(np, 0); + struct clk *clk; + struct clk_sama5d4_slow_osc *osc; + const char *parent_names[2] = { "slow_rc_osc", "slow_osc" }; + bool bypass; + int ret; + + if (!regbase) + return -ENOMEM; + + clk = clk_fixed(parent_names[0], 32768); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + bypass = of_property_read_bool(np, "atmel,osc-bypass"); + + osc = xzalloc(sizeof(*osc)); + osc->parent_name = of_clk_get_parent_name(np, 0); + osc->clk.name = parent_names[1]; + osc->clk.ops = &sama5d4_slow_osc_ops; + osc->clk.parent_names = &osc->parent_name; + osc->clk.num_parents = 1; + osc->sckcr = regbase; + osc->startup_usec = 1200000; + + if (bypass) + writel((readl(regbase) | AT91_SCKC_OSC32BYP), regbase); + + ret = clk_register(&osc->clk); + if (ret) { + kfree(osc); + return ret; + } + + clk = at91_clk_register_sam9x5_slow(regbase, "slowck", parent_names, 2); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + return of_clk_add_provider(np, of_clk_src_simple_get, clk); +} +CLK_OF_DECLARE(sama5d4_clk_sckc, "atmel,sama5d4-sckc", + of_sama5d4_sckc_setup); diff --git a/drivers/clocksource/timer-atmel-pit.c b/drivers/clocksource/timer-atmel-pit.c index cc7ad2f..947a1e7 100644 --- a/drivers/clocksource/timer-atmel-pit.c +++ b/drivers/clocksource/timer-atmel-pit.c @@ -102,9 +102,18 @@ return init_clock(&cs); } +const static __maybe_unused struct of_device_id at91_pit_dt_ids[] = { + { + .compatible = "atmel,at91sam9260-pit", + }, { + /* sentinel */ + } +}; + static struct driver_d at91_pit_driver = { .name = "at91-pit", .probe = at91_pit_probe, + .of_compatible = DRV_OF_COMPAT(at91_pit_dt_ids), }; static int at91_pit_init(void) diff --git a/drivers/mci/atmel_mci.c b/drivers/mci/atmel_mci.c index 2a0ddb0..317cf46 100644 --- a/drivers/mci/atmel_mci.c +++ b/drivers/mci/atmel_mci.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "atmel-mci-regs.h" @@ -53,6 +54,7 @@ u32 cfg_reg; u32 sdc_reg; bool need_reset; + int detect_pin; }; #define to_mci_host(mci) container_of(mci, struct atmel_mci, mci) @@ -360,14 +362,13 @@ static int atmci_card_present(struct mci_host *mci) { struct atmel_mci *host = to_mci_host(mci); - struct atmel_mci_platform_data *pd = host->hw_dev->platform_data; int ret; /* No gpio, assume card is present */ - if (!gpio_is_valid(pd->detect_pin)) + if (!gpio_is_valid(host->detect_pin)) return 1; - ret = gpio_get_value(pd->detect_pin); + ret = gpio_get_value(host->detect_pin); return ret == 0 ? 1 : 0; } @@ -535,43 +536,70 @@ { struct resource *iores; struct atmel_mci *host; + struct device_node *np = hw_dev->device_node; struct atmel_mci_platform_data *pd = hw_dev->platform_data; int ret; - if (!pd) { - dev_err(hw_dev, "missing platform data\n"); - return -EINVAL; - } - - if (gpio_is_valid(pd->detect_pin)) { - ret = gpio_request(pd->detect_pin, "mci_cd"); - if (ret) { - dev_err(hw_dev, "Impossible to request CD gpio %d (%d)\n", - ret, pd->detect_pin); - return ret; - } - - ret = gpio_direction_input(pd->detect_pin); - if (ret) { - dev_err(hw_dev, "Impossible to configure CD gpio %d as input (%d)\n", - ret, pd->detect_pin); - goto err_gpio_cd_request; - } - } - host = xzalloc(sizeof(*host)); host->mci.send_cmd = atmci_request; host->mci.set_ios = atmci_set_ios; host->mci.init = atmci_reset; host->mci.card_present = atmci_card_present; host->mci.hw_dev = hw_dev; - host->mci.devname = pd->devname; + host->detect_pin = -EINVAL; - if (pd->bus_width >= 4) - host->mci.host_caps |= MMC_CAP_4_BIT_DATA; - if (pd->bus_width == 8) - host->mci.host_caps |= MMC_CAP_8_BIT_DATA; - host->slot_b = pd->slot_b; + if (pd) { + host->detect_pin = pd->detect_pin; + host->mci.devname = pd->devname; + + if (pd->bus_width >= 4) + host->mci.host_caps |= MMC_CAP_4_BIT_DATA; + if (pd->bus_width == 8) + host->mci.host_caps |= MMC_CAP_8_BIT_DATA; + + host->slot_b = pd->slot_b; + } else if (np) { + u32 slot_id; + struct device_node *cnp; + const char *alias = of_alias_get(np); + + if (alias) + host->mci.devname = xstrdup(alias); + + host->detect_pin = of_get_named_gpio(np, "cd-gpios", 0); + + for_each_child_of_node(np, cnp) { + if (of_property_read_u32(cnp, "reg", &slot_id)) { + dev_warn(hw_dev, "reg property is missing for %s\n", + cnp->full_name); + continue; + } + + host->slot_b = slot_id; + mci_of_parse_node(&host->mci, cnp); + break; + } + } else { + dev_err(hw_dev, "Missing device information\n"); + ret = -EINVAL; + goto error_out; + } + + if (gpio_is_valid(host->detect_pin)) { + ret = gpio_request(host->detect_pin, "mci_cd"); + if (ret) { + dev_err(hw_dev, "Impossible to request CD gpio %d (%d)\n", + ret, host->detect_pin); + goto error_out; + } + + ret = gpio_direction_input(host->detect_pin); + if (ret) { + dev_err(hw_dev, "Impossible to configure CD gpio %d as input (%d)\n", + ret, host->detect_pin); + goto error_out; + } + } iores = dev_request_mem_resource(hw_dev, 0); if (IS_ERR(iores)) @@ -583,7 +611,7 @@ if (IS_ERR(host->clk)) { dev_err(hw_dev, "no mci_clk\n"); ret = PTR_ERR(host->clk); - goto err_gpio_cd_request; + goto error_out; } clk_enable(host->clk); @@ -614,15 +642,26 @@ return 0; -err_gpio_cd_request: - if (gpio_is_valid(pd->detect_pin)) - gpio_free(pd->detect_pin); +error_out: + free(host); + + if (gpio_is_valid(host->detect_pin)) + gpio_free(host->detect_pin); return ret; } +static __maybe_unused struct of_device_id atmci_compatible[] = { + { + .compatible = "atmel,hsmci", + }, { + /* sentinel */ + } +}; + static struct driver_d atmci_driver = { .name = "atmel_mci", .probe = atmci_probe, + .of_compatible = DRV_OF_COMPAT(atmci_compatible), }; device_platform_driver(atmci_driver); diff --git a/drivers/mci/mci-core.c b/drivers/mci/mci-core.c index 055a5e2..928277a 100644 --- a/drivers/mci/mci-core.c +++ b/drivers/mci/mci-core.c @@ -1843,20 +1843,18 @@ return ret; } -void mci_of_parse(struct mci_host *host) +void mci_of_parse_node(struct mci_host *host, + struct device_node *np) { - struct device_node *np; u32 bus_width; u32 dsr_val; if (!IS_ENABLED(CONFIG_OFDEVICE)) return; - if (!host->hw_dev || !host->hw_dev->device_node) + if (!host->hw_dev || !np) return; - np = host->hw_dev->device_node; - /* "bus-width" is translated to MMC_CAP_*_BIT_DATA flags */ if (of_property_read_u32(np, "bus-width", &bus_width) < 0) { /* If bus-width is missing we get the driver's default, which @@ -1897,6 +1895,11 @@ host->non_removable = of_property_read_bool(np, "non-removable"); } +void mci_of_parse(struct mci_host *host) +{ + return mci_of_parse_node(host, host->hw_dev->device_node); +} + struct mci *mci_get_device_by_name(const char *name) { struct mci *mci; diff --git a/drivers/net/macb.c b/drivers/net/macb.c index 5f2e5e5..739a3df 100644 --- a/drivers/net/macb.c +++ b/drivers/net/macb.c @@ -48,6 +48,7 @@ #include #include #include +#include #include "macb.h" @@ -615,14 +616,8 @@ struct resource *iores; struct eth_device *edev; struct macb_device *macb; + const char *pclk_name; u32 ncfgr; - struct macb_platform_data *pdata; - - if (!dev->platform_data) { - dev_err(dev, "macb: no platform_data\n"); - return -ENODEV; - } - pdata = dev->platform_data; edev = xzalloc(sizeof(struct eth_device) + sizeof(struct macb_device)); edev->priv = (struct macb_device *)(edev + 1); @@ -633,22 +628,49 @@ edev->open = macb_open; edev->send = macb_send; edev->halt = macb_halt; - edev->get_ethaddr = pdata->get_ethaddr ? pdata->get_ethaddr : macb_get_ethaddr; + edev->get_ethaddr = macb_get_ethaddr; edev->set_ethaddr = macb_set_ethaddr; edev->parent = dev; macb->miibus.read = macb_phy_read; macb->miibus.write = macb_phy_write; - macb->phy_addr = pdata->phy_addr; macb->miibus.priv = macb; macb->miibus.parent = dev; - if (pdata->phy_interface == PHY_INTERFACE_MODE_NA) - macb->interface = PHY_INTERFACE_MODE_MII; - else - macb->interface = pdata->phy_interface; + if (dev->platform_data) { + struct macb_platform_data *pdata = dev->platform_data; - macb->phy_flags = pdata->phy_flags; + if (pdata->phy_interface == PHY_INTERFACE_MODE_NA) + macb->interface = PHY_INTERFACE_MODE_MII; + else + macb->interface = pdata->phy_interface; + + if (pdata->get_ethaddr) + edev->get_ethaddr = pdata->get_ethaddr; + + macb->phy_addr = pdata->phy_addr; + macb->phy_flags = pdata->phy_flags; + pclk_name = "macb_clk"; + } else if (IS_ENABLED(CONFIG_OFDEVICE) && dev->device_node) { + int ret; + struct device_node *mdiobus; + + ret = of_get_phy_mode(dev->device_node); + if (ret < 0) + macb->interface = PHY_INTERFACE_MODE_MII; + else + macb->interface = ret; + + mdiobus = of_get_child_by_name(dev->device_node, "mdio"); + if (mdiobus) + macb->miibus.dev.device_node = mdiobus; + + macb->phy_addr = -1; + pclk_name = NULL; + } else { + dev_err(dev, "macb: no platform_data\n"); + return -ENODEV; + } iores = dev_request_mem_resource(dev, 0); if (IS_ERR(iores)) @@ -698,8 +720,14 @@ return 0; } +static const struct of_device_id macb_dt_ids[] = { + { .compatible = "cdns,at91sam9260-macb",}, + { /* sentinel */ } +}; + static struct driver_d macb_driver = { .name = "macb", .probe = macb_probe, + .of_compatible = DRV_OF_COMPAT(macb_dt_ids), }; device_platform_driver(macb_driver); diff --git a/drivers/serial/atmel.c b/drivers/serial/atmel.c index ab94fd2..2f8adc9 100644 --- a/drivers/serial/atmel.c +++ b/drivers/serial/atmel.c @@ -446,8 +446,15 @@ return 0; } +static const struct of_device_id __maybe_unused atmel_serial_dt_ids[] = { + { .compatible = "atmel,at91rm9200-usart" }, + { .compatible = "atmel,at91sam9260-usart" }, + { /* sentinel */ } +}; + static struct driver_d atmel_serial_driver = { .name = "atmel_usart", .probe = atmel_serial_probe, + .of_compatible = DRV_OF_COMPAT(atmel_serial_dt_ids), }; console_platform_driver(atmel_serial_driver); diff --git a/drivers/spi/atmel_spi.c b/drivers/spi/atmel_spi.c index ef57867..446e1d9 100644 --- a/drivers/spi/atmel_spi.c +++ b/drivers/spi/atmel_spi.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -400,10 +401,11 @@ int ret = 0; int i; struct spi_master *master; + struct device_node *node = dev->device_node; struct atmel_spi *as; struct at91_spi_platform_data *pdata = dev->platform_data; - if (!pdata) { + if (!IS_ENABLED(CONFIG_OFDEVICE) && !pdata) { dev_err(dev, "missing platform data\n"); return -EINVAL; } @@ -414,6 +416,23 @@ master->dev = dev; master->bus_num = dev->id; + if (pdata) { + master->num_chipselect = pdata->num_chipselect; + as->cs_pins = pdata->chipselect; + } else { + master->num_chipselect = of_gpio_named_count(node, "cs-gpios"); + as->cs_pins = xzalloc(sizeof(u32) * master->num_chipselect); + + for (i = 0; i < master->num_chipselect; i++) { + as->cs_pins[i] = of_get_named_gpio(node, "cs-gpios", i); + + if (!gpio_is_valid(as->cs_pins[i])) + break; + } + + master->num_chipselect = i; + } + as->clk = clk_get(dev, "spi_clk"); if (IS_ERR(as->clk)) { dev_err(dev, "no spi_clk\n"); @@ -423,8 +442,6 @@ master->setup = atmel_spi_setup; master->transfer = atmel_spi_transfer; - master->num_chipselect = pdata->num_chipselect; - as->cs_pins = pdata->chipselect; iores = dev_request_mem_resource(dev, 0); if (IS_ERR(iores)) return PTR_ERR(iores); @@ -465,8 +482,14 @@ return ret; } +const static __maybe_unused const struct of_device_id atmel_spi_dt_ids[] = { + { .compatible = "atmel,at91rm9200-spi" }, + { /* sentinel */ } +}; + static struct driver_d atmel_spi_driver = { .name = "atmel_spi", .probe = atmel_spi_probe, + .of_compatible = DRV_OF_COMPAT(atmel_spi_dt_ids), }; device_platform_driver(atmel_spi_driver); diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 54eaf46..db44052 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -10,20 +10,17 @@ config USB_EHCI_ATMEL depends on ARCH_AT91 depends on USB_EHCI + select USB_OHCI_AT91 bool "Atmel EHCI driver" config USB_OHCI bool "OHCI driver" depends on !MMU -if USB_OHCI - config USB_OHCI_AT91 depends on ARCH_AT91 bool "AT91 OHCI driver" -endif - config USB_XHCI bool "xHCI driver" depends on HAS_DMA diff --git a/drivers/usb/host/ehci-atmel.c b/drivers/usb/host/ehci-atmel.c index f075b50..1132879 100644 --- a/drivers/usb/host/ehci-atmel.c +++ b/drivers/usb/host/ehci-atmel.c @@ -65,6 +65,9 @@ struct resource *iores; struct ehci_data data; struct atmel_ehci_priv *atehci; + const char *uclk_name; + + uclk_name = (dev->device_node) ? "usb_clk" : "uhpck"; atehci = xzalloc(sizeof(*atehci)); atehci->dev = dev; @@ -76,7 +79,7 @@ return -ENOENT; } - atehci->uclk = clk_get(dev, "uhpck"); + atehci->uclk = clk_get(dev, uclk_name); if (IS_ERR(atehci->iclk)) { dev_err(dev, "Error getting function clock\n"); return -ENOENT; @@ -107,9 +110,15 @@ atmel_stop_clock(dev->priv); } +static const struct of_device_id atmel_ehci_dt_ids[] = { + { .compatible = "atmel,at91sam9g45-ehci" }, + { /* sentinel */ } +}; + static struct driver_d atmel_ehci_driver = { .name = "atmel-ehci", .probe = atmel_ehci_probe, .remove = atmel_ehci_remove, + .of_compatible = DRV_OF_COMPAT(atmel_ehci_dt_ids), }; device_platform_driver(atmel_ehci_driver); diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index 5f74526..1013ba3 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -23,10 +23,18 @@ #include #include #include +#include +#include #include +#include + #include "ohci.h" +#define at91_for_each_port(index) \ + for ((index) = 0; (index) < AT91_MAX_USBH_PORTS; (index)++) + + struct ohci_at91_priv { struct device_d *dev; struct clk *iclk; @@ -59,6 +67,59 @@ clk_disable(ohci_at91->iclk); } +static int at91_ohci_probe_dt(struct device_d *dev) +{ + u32 ports; + int i, ret, gpio; + enum of_gpio_flags flags; + struct at91_usbh_data *pdata; + struct device_node *np = dev->device_node; + + pdata = xzalloc(sizeof(*pdata)); + dev->platform_data = pdata; + + if (!of_property_read_u32(np, "num-ports", &ports)) { + pdata->ports = ports; + } else { + dev_err(dev, "Failed to read 'num-ports' property\n"); + return -EINVAL; + } + + at91_for_each_port(i) { + /* + * do not configure PIO if not in relation with + * real USB port on board + */ + if (i >= pdata->ports) { + pdata->vbus_pin[i] = -EINVAL; + continue; + } + + gpio = of_get_named_gpio_flags(np, "atmel,vbus-gpio", i, + &flags); + pdata->vbus_pin[i] = gpio; + if (!gpio_is_valid(gpio)) + continue; + pdata->vbus_pin_active_low[i] = flags & OF_GPIO_ACTIVE_LOW; + + ret = gpio_request(gpio, "ohci_vbus"); + if (ret) { + dev_err(dev, "can't request vbus gpio %d\n", gpio); + continue; + } + ret = gpio_direction_output(gpio, + !pdata->vbus_pin_active_low[i]); + if (ret) { + dev_err(dev, "can't put vbus gpio %d as output %d\n", + gpio, !pdata->vbus_pin_active_low[i]); + gpio_free(gpio); + continue; + } + } + + return 0; +} + static int at91_ohci_probe(struct device_d *dev) { int ret; @@ -68,6 +129,12 @@ dev->priv = ohci_at91; ohci_at91->dev = dev; + if (dev->device_node) { + ret = at91_ohci_probe_dt(dev); + if (ret < 0) + return ret; + } + io = dev_get_resource(dev, IORESOURCE_MEM, 0); if (IS_ERR(io)) { dev_err(dev, "Failed to get IORESOURCE_MEM\n"); @@ -75,13 +142,13 @@ } ohci_at91->regs = IOMEM(io->start); - ohci_at91->iclk = clk_get(NULL, "ohci_clk"); + ohci_at91->iclk = clk_get(dev, "ohci_clk"); if (IS_ERR(ohci_at91->iclk)) { dev_err(dev, "Failed to get 'ohci_clk'\n"); return PTR_ERR(ohci_at91->iclk); } - ohci_at91->fclk = clk_get(NULL, "uhpck"); + ohci_at91->fclk = clk_get(dev, "uhpck"); if (IS_ERR(ohci_at91->fclk)) { dev_err(dev, "Failed to get 'uhpck'\n"); return PTR_ERR(ohci_at91->fclk); @@ -107,6 +174,7 @@ static void at91_ohci_remove(struct device_d *dev) { + struct at91_usbh_data *pdata = dev->platform_data; struct ohci_at91_priv *ohci_at91 = dev->priv; /* @@ -118,11 +186,32 @@ * Stop the USB clocks. */ at91_stop_clock(ohci_at91); + + if (pdata) { + bool active_low; + int i, gpio; + + at91_for_each_port(i) { + gpio = pdata->vbus_pin[i]; + active_low = pdata->vbus_pin_active_low[i]; + + if (gpio_is_valid(gpio)) { + gpio_set_value(gpio, active_low); + gpio_free(gpio); + } + } + } } +static const struct of_device_id at91_ohci_dt_ids[] = { + { .compatible = "atmel,at91rm9200-ohci" }, + { /* sentinel */ } +}; + static struct driver_d at91_ohci_driver = { .name = "at91_ohci", .probe = at91_ohci_probe, .remove = at91_ohci_remove, + .of_compatible = DRV_OF_COMPAT(at91_ohci_dt_ids), }; device_platform_driver(at91_ohci_driver); diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c index 946e9d3..916027e 100644 --- a/drivers/w1/masters/w1-gpio.c +++ b/drivers/w1/masters/w1-gpio.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "../w1.h" @@ -43,12 +44,58 @@ return gpio_get_value(pdata->pin) ? 1 : 0; } +static int w1_gpio_probe_dt(struct device_d *dev) +{ + struct w1_gpio_platform_data *pdata; + struct device_node *np = dev->device_node; + int gpio; + + if (dev->platform_data) + return 0; + + pdata = xzalloc(sizeof(*pdata)); + + if (of_get_property(np, "linux,open-drain", NULL)) + pdata->is_open_drain = 1; + + gpio = of_get_gpio(np, 0); + if (!gpio_is_valid(gpio)) { + if (gpio != -EPROBE_DEFER) + dev_err(dev, + "Failed to parse gpio property for data pin (%d)\n", + gpio); + + goto free_pdata; + } + pdata->pin = gpio; + + gpio = of_get_gpio(np, 1); + if (gpio == -EPROBE_DEFER) + goto free_pdata; + + /* ignore other errors as the pullup gpio is optional */ + pdata->ext_pullup_enable_pin = gpio; + + dev->platform_data = pdata; + return 0; + +free_pdata: + free(pdata); + return gpio; +} + static int __init w1_gpio_probe(struct device_d *dev) { struct w1_bus *master; struct w1_gpio_platform_data *pdata; int err; + if (IS_ENABLED(CONFIG_OFDEVICE)) { + err = w1_gpio_probe_dt(dev); + if (err < 0) + return err; + } + pdata = dev->platform_data; if (!pdata) @@ -104,8 +151,14 @@ return err; } +static __maybe_unused const struct of_device_id w1_gpio_dt_ids[] = { + { .compatible = "w1-gpio" }, + {} +}; + static struct driver_d w1_gpio_driver = { .name = "w1-gpio", .probe = w1_gpio_probe, + .of_compatible = DRV_OF_COMPAT(w1_gpio_dt_ids), }; device_platform_driver(w1_gpio_driver); diff --git a/images/Makefile b/images/Makefile index 68876e5..adf950a 100644 --- a/images/Makefile +++ b/images/Makefile @@ -111,6 +111,7 @@ include $(srctree)/images/Makefile.rockchip include $(srctree)/images/Makefile.socfpga include $(srctree)/images/Makefile.tegra +include $(srctree)/images/Makefile.at91 targets += $(image-y) pbl.lds barebox.x barebox.z targets += $(patsubst %,%.pblx,$(pblx-y)) diff --git a/images/Makefile.at91 b/images/Makefile.at91 new file mode 100644 index 0000000..686d501 --- /dev/null +++ b/images/Makefile.at91 @@ -0,0 +1,7 @@ +# +# barebox image generation Makefile for AT91 images +# + +pblx-$(MACH_AT91SAM9X5EK) += start_at91sam9x5ek +FILE_barebox-at91sam9x5ek.img = start_at91sam9x5ek.pblx +image-$(MACH_AT91SAM9X5EK) += barebox-at91sam9x5ek.img diff --git a/include/linux/clk/at91_pmc.h b/include/linux/clk/at91_pmc.h new file mode 100644 index 0000000..17f413b --- /dev/null +++ b/include/linux/clk/at91_pmc.h @@ -0,0 +1,188 @@ +/* + * include/linux/clk/at91_pmc.h + * + * Copyright (C) 2005 Ivan Kokshaysky + * Copyright (C) SAN People + * + * Power Management Controller (PMC) - System peripherals registers. + * Based on AT91RM9200 datasheet revision E. + * + * 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. + */ + +#ifndef AT91_PMC_H +#define AT91_PMC_H + +#define AT91_PMC_SCER 0x00 /* System Clock Enable Register */ +#define AT91_PMC_SCDR 0x04 /* System Clock Disable Register */ + +#define AT91_PMC_SCSR 0x08 /* System Clock Status Register */ +#define AT91_PMC_PCK (1 << 0) /* Processor Clock */ +#define AT91RM9200_PMC_UDP (1 << 1) /* USB Devcice Port Clock [AT91RM9200 only] */ +#define AT91RM9200_PMC_MCKUDP (1 << 2) /* USB Device Port Master Clock Automatic Disable on Suspend [AT91RM9200 only] */ +#define AT91RM9200_PMC_UHP (1 << 4) /* USB Host Port Clock [AT91RM9200 only] */ +#define AT91SAM926x_PMC_UHP (1 << 6) /* USB Host Port Clock [AT91SAM926x only] */ +#define AT91SAM926x_PMC_UDP (1 << 7) /* USB Devcice Port Clock [AT91SAM926x only] */ +#define AT91_PMC_PCK0 (1 << 8) /* Programmable Clock 0 */ +#define AT91_PMC_PCK1 (1 << 9) /* Programmable Clock 1 */ +#define AT91_PMC_PCK2 (1 << 10) /* Programmable Clock 2 */ +#define AT91_PMC_PCK3 (1 << 11) /* Programmable Clock 3 */ +#define AT91_PMC_PCK4 (1 << 12) /* Programmable Clock 4 [AT572D940HF only] */ +#define AT91_PMC_HCK0 (1 << 16) /* AHB Clock (USB host) [AT91SAM9261 only] */ +#define AT91_PMC_HCK1 (1 << 17) /* AHB Clock (LCD) [AT91SAM9261 only] */ + +#define AT91_PMC_PCER 0x10 /* Peripheral Clock Enable Register */ +#define AT91_PMC_PCDR 0x14 /* Peripheral Clock Disable Register */ +#define AT91_PMC_PCSR 0x18 /* Peripheral Clock Status Register */ + +#define AT91_CKGR_UCKR 0x1C /* UTMI Clock Register [some SAM9] */ +#define AT91_PMC_UPLLEN (1 << 16) /* UTMI PLL Enable */ +#define AT91_PMC_UPLLCOUNT (0xf << 20) /* UTMI PLL Start-up Time */ +#define AT91_PMC_BIASEN (1 << 24) /* UTMI BIAS Enable */ +#define AT91_PMC_BIASCOUNT (0xf << 28) /* UTMI BIAS Start-up Time */ + +#define AT91_CKGR_MOR 0x20 /* Main Oscillator Register [not on SAM9RL] */ +#define AT91_PMC_MOSCEN (1 << 0) /* Main Oscillator Enable */ +#define AT91_PMC_OSCBYPASS (1 << 1) /* Oscillator Bypass */ +#define AT91_PMC_MOSCRCEN (1 << 3) /* Main On-Chip RC Oscillator Enable [some SAM9] */ +#define AT91_PMC_OSCOUNT (0xff << 8) /* Main Oscillator Start-up Time */ +#define AT91_PMC_KEY (0x37 << 16) /* MOR Writing Key */ +#define AT91_PMC_MOSCSEL (1 << 24) /* Main Oscillator Selection [some SAM9] */ +#define AT91_PMC_CFDEN (1 << 25) /* Clock Failure Detector Enable [some SAM9] */ + +#define AT91_CKGR_MCFR 0x24 /* Main Clock Frequency Register */ +#define AT91_PMC_MAINF (0xffff << 0) /* Main Clock Frequency */ +#define AT91_PMC_MAINRDY (1 << 16) /* Main Clock Ready */ + +#define AT91_CKGR_PLLAR 0x28 /* PLL A Register */ +#define AT91_CKGR_PLLBR 0x2c /* PLL B Register */ +#define AT91_PMC_DIV (0xff << 0) /* Divider */ +#define AT91_PMC_PLLCOUNT (0x3f << 8) /* PLL Counter */ +#define AT91_PMC_OUT (3 << 14) /* PLL Clock Frequency Range */ +#define AT91_PMC_MUL (0x7ff << 16) /* PLL Multiplier */ +#define AT91_PMC_MUL_GET(n) ((n) >> 16 & 0x7ff) +#define AT91_PMC3_MUL (0x7f << 18) /* PLL Multiplier [SAMA5 only] */ +#define AT91_PMC3_MUL_GET(n) ((n) >> 18 & 0x7f) +#define AT91_PMC_USBDIV (3 << 28) /* USB Divisor (PLLB only) */ +#define AT91_PMC_USBDIV_1 (0 << 28) +#define AT91_PMC_USBDIV_2 (1 << 28) +#define AT91_PMC_USBDIV_4 (2 << 28) +#define AT91_PMC_USB96M (1 << 28) /* Divider by 2 Enable (PLLB only) */ + +#define AT91_PMC_MCKR 0x30 /* Master Clock Register */ +#define AT91_PMC_CSS (3 << 0) /* Master Clock Selection */ +#define AT91_PMC_CSS_SLOW (0 << 0) +#define AT91_PMC_CSS_MAIN (1 << 0) +#define AT91_PMC_CSS_PLLA (2 << 0) +#define AT91_PMC_CSS_PLLB (3 << 0) +#define AT91_PMC_CSS_UPLL (3 << 0) /* [some SAM9 only] */ +#define PMC_PRES_OFFSET 2 +#define AT91_PMC_PRES (7 << PMC_PRES_OFFSET) /* Master Clock Prescaler */ +#define AT91_PMC_PRES_1 (0 << PMC_PRES_OFFSET) +#define AT91_PMC_PRES_2 (1 << PMC_PRES_OFFSET) +#define AT91_PMC_PRES_4 (2 << PMC_PRES_OFFSET) +#define AT91_PMC_PRES_8 (3 << PMC_PRES_OFFSET) +#define AT91_PMC_PRES_16 (4 << PMC_PRES_OFFSET) +#define AT91_PMC_PRES_32 (5 << PMC_PRES_OFFSET) +#define AT91_PMC_PRES_64 (6 << PMC_PRES_OFFSET) +#define PMC_ALT_PRES_OFFSET 4 +#define AT91_PMC_ALT_PRES (7 << PMC_ALT_PRES_OFFSET) /* Master Clock Prescaler [alternate location] */ +#define AT91_PMC_ALT_PRES_1 (0 << PMC_ALT_PRES_OFFSET) +#define AT91_PMC_ALT_PRES_2 (1 << PMC_ALT_PRES_OFFSET) +#define AT91_PMC_ALT_PRES_4 (2 << PMC_ALT_PRES_OFFSET) +#define AT91_PMC_ALT_PRES_8 (3 << PMC_ALT_PRES_OFFSET) +#define AT91_PMC_ALT_PRES_16 (4 << PMC_ALT_PRES_OFFSET) +#define AT91_PMC_ALT_PRES_32 (5 << PMC_ALT_PRES_OFFSET) +#define AT91_PMC_ALT_PRES_64 (6 << PMC_ALT_PRES_OFFSET) +#define AT91_PMC_MDIV (3 << 8) /* Master Clock Division */ +#define AT91RM9200_PMC_MDIV_1 (0 << 8) /* [AT91RM9200 only] */ +#define AT91RM9200_PMC_MDIV_2 (1 << 8) +#define AT91RM9200_PMC_MDIV_3 (2 << 8) +#define AT91RM9200_PMC_MDIV_4 (3 << 8) +#define AT91SAM9_PMC_MDIV_1 (0 << 8) /* [SAM9 only] */ +#define AT91SAM9_PMC_MDIV_2 (1 << 8) +#define AT91SAM9_PMC_MDIV_4 (2 << 8) +#define AT91SAM9_PMC_MDIV_6 (3 << 8) /* [some SAM9 only] */ +#define AT91SAM9_PMC_MDIV_3 (3 << 8) /* [some SAM9 only] */ +#define AT91_PMC_PDIV (1 << 12) /* Processor Clock Division [some SAM9 only] */ +#define AT91_PMC_PDIV_1 (0 << 12) +#define AT91_PMC_PDIV_2 (1 << 12) +#define AT91_PMC_PLLADIV2 (1 << 12) /* PLLA divisor by 2 [some SAM9 only] */ +#define AT91_PMC_PLLADIV2_OFF (0 << 12) +#define AT91_PMC_PLLADIV2_ON (1 << 12) +#define AT91_PMC_H32MXDIV BIT(24) + +#define AT91_PMC_USB 0x38 /* USB Clock Register [some SAM9 only] */ +#define AT91_PMC_USBS (0x1 << 0) /* USB OHCI Input clock selection */ +#define AT91_PMC_USBS_PLLA (0 << 0) +#define AT91_PMC_USBS_UPLL (1 << 0) +#define AT91_PMC_USBS_PLLB (1 << 0) /* [AT91SAMN12 only] */ +#define AT91_PMC_OHCIUSBDIV (0xF << 8) /* Divider for USB OHCI Clock */ +#define AT91_PMC_OHCIUSBDIV_1 (0x0 << 8) +#define AT91_PMC_OHCIUSBDIV_2 (0x1 << 8) + +#define AT91_PMC_SMD 0x3c /* Soft Modem Clock Register [some SAM9 only] */ +#define AT91_PMC_SMDS (0x1 << 0) /* SMD input clock selection */ +#define AT91_PMC_SMD_DIV (0x1f << 8) /* SMD input clock divider */ +#define AT91_PMC_SMDDIV(n) (((n) << 8) & AT91_PMC_SMD_DIV) + +#define AT91_PMC_PCKR(n) (0x40 + ((n) * 4)) /* Programmable Clock 0-N Registers */ +#define AT91_PMC_ALT_PCKR_CSS (0x7 << 0) /* Programmable Clock Source Selection [alternate length] */ +#define AT91_PMC_CSS_MASTER (4 << 0) /* [some SAM9 only] */ +#define AT91_PMC_CSSMCK (0x1 << 8) /* CSS or Master Clock Selection */ +#define AT91_PMC_CSSMCK_CSS (0 << 8) +#define AT91_PMC_CSSMCK_MCK (1 << 8) + +#define AT91_PMC_IER 0x60 /* Interrupt Enable Register */ +#define AT91_PMC_IDR 0x64 /* Interrupt Disable Register */ +#define AT91_PMC_SR 0x68 /* Status Register */ +#define AT91_PMC_MOSCS (1 << 0) /* MOSCS Flag */ +#define AT91_PMC_LOCKA (1 << 1) /* PLLA Lock */ +#define AT91_PMC_LOCKB (1 << 2) /* PLLB Lock */ +#define AT91_PMC_MCKRDY (1 << 3) /* Master Clock */ +#define AT91_PMC_LOCKU (1 << 6) /* UPLL Lock [some SAM9] */ +#define AT91_PMC_OSCSEL (1 << 7) /* Slow Oscillator Selection [some SAM9] */ +#define AT91_PMC_PCK0RDY (1 << 8) /* Programmable Clock 0 */ +#define AT91_PMC_PCK1RDY (1 << 9) /* Programmable Clock 1 */ +#define AT91_PMC_PCK2RDY (1 << 10) /* Programmable Clock 2 */ +#define AT91_PMC_PCK3RDY (1 << 11) /* Programmable Clock 3 */ +#define AT91_PMC_MOSCSELS (1 << 16) /* Main Oscillator Selection [some SAM9] */ +#define AT91_PMC_MOSCRCS (1 << 17) /* Main On-Chip RC [some SAM9] */ +#define AT91_PMC_CFDEV (1 << 18) /* Clock Failure Detector Event [some SAM9] */ +#define AT91_PMC_GCKRDY (1 << 24) /* Generated Clocks */ +#define AT91_PMC_IMR 0x6c /* Interrupt Mask Register */ + +#define AT91_PMC_PLLICPR 0x80 /* PLL Charge Pump Current Register */ + +#define AT91_PMC_PROT 0xe4 /* Write Protect Mode Register [some SAM9] */ +#define AT91_PMC_WPEN (0x1 << 0) /* Write Protect Enable */ +#define AT91_PMC_WPKEY (0xffffff << 8) /* Write Protect Key */ +#define AT91_PMC_PROTKEY (0x504d43 << 8) /* Activation Code */ + +#define AT91_PMC_WPSR 0xe8 /* Write Protect Status Register [some SAM9] */ +#define AT91_PMC_WPVS (0x1 << 0) /* Write Protect Violation Status */ +#define AT91_PMC_WPVSRC (0xffff << 8) /* Write Protect Violation Source */ + +#define AT91_PMC_PCER1 0x100 /* Peripheral Clock Enable Register 1 [SAMA5 only]*/ +#define AT91_PMC_PCDR1 0x104 /* Peripheral Clock Enable Register 1 */ +#define AT91_PMC_PCSR1 0x108 /* Peripheral Clock Enable Register 1 */ + +#define AT91_PMC_PCR 0x10c /* Peripheral Control Register [some SAM9 and SAMA5] */ +#define AT91_PMC_PCR_PID_MASK 0x3f +#define AT91_PMC_PCR_GCKCSS_OFFSET 8 +#define AT91_PMC_PCR_GCKCSS_MASK (0x7 << AT91_PMC_PCR_GCKCSS_OFFSET) +#define AT91_PMC_PCR_GCKCSS(n) ((n) << AT91_PMC_PCR_GCKCSS_OFFSET) /* GCK Clock Source Selection */ +#define AT91_PMC_PCR_CMD (0x1 << 12) /* Command (read=0, write=1) */ +#define AT91_PMC_PCR_DIV_OFFSET 16 +#define AT91_PMC_PCR_DIV_MASK (0x3 << AT91_PMC_PCR_DIV_OFFSET) +#define AT91_PMC_PCR_DIV(n) ((n) << AT91_PMC_PCR_DIV_OFFSET) /* Divisor Value */ +#define AT91_PMC_PCR_GCKDIV_OFFSET 20 +#define AT91_PMC_PCR_GCKDIV_MASK (0xff << AT91_PMC_PCR_GCKDIV_OFFSET) +#define AT91_PMC_PCR_GCKDIV(n) ((n) << AT91_PMC_PCR_GCKDIV_OFFSET) /* Generated Clock Divisor Value */ +#define AT91_PMC_PCR_EN (0x1 << 28) /* Enable */ +#define AT91_PMC_PCR_GCKEN (0x1 << 29) /* GCK Enable */ + +#endif diff --git a/include/mci.h b/include/mci.h index cc4712c..781e6e0 100644 --- a/include/mci.h +++ b/include/mci.h @@ -480,6 +480,7 @@ int mci_register(struct mci_host*); void mci_of_parse(struct mci_host *host); +void mci_of_parse_node(struct mci_host *host, struct device_node *np); int mci_detect_card(struct mci_host *); int mci_send_ext_csd(struct mci *mci, char *ext_csd); int mci_switch(struct mci *mci, unsigned set, unsigned index,