| /* |
| * Copyright (c) 2020, Seagate Technology LLC |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nxp_lpc11u6x_syscon |
| |
| #include <zephyr/devicetree.h> |
| #include <zephyr/device.h> |
| #include <zephyr/kernel.h> |
| |
| #include <zephyr/drivers/clock_control/lpc11u6x_clock_control.h> |
| |
| #include "clock_control_lpc11u6x.h" |
| |
| static void syscon_power_up(struct lpc11u6x_syscon_regs *syscon, |
| uint32_t bit, bool enable) |
| { |
| if (enable) { |
| syscon->pd_run_cfg = (syscon->pd_run_cfg & ~bit) |
| | LPC11U6X_PDRUNCFG_MASK; |
| } else { |
| syscon->pd_run_cfg = syscon->pd_run_cfg | bit |
| | LPC11U6X_PDRUNCFG_MASK; |
| } |
| } |
| |
| static void syscon_set_pll_src(struct lpc11u6x_syscon_regs *syscon, |
| uint32_t src) |
| { |
| syscon->sys_pll_clk_sel = src; |
| syscon->sys_pll_clk_uen = 0; |
| syscon->sys_pll_clk_uen = 1; |
| } |
| |
| static void set_flash_access_time(uint32_t nr_cycles) |
| { |
| uint32_t *reg = (uint32_t *) LPC11U6X_FLASH_TIMING_REG; |
| |
| *reg = (*reg & (~LPC11U6X_FLASH_TIMING_MASK)) | nr_cycles; |
| } |
| |
| static void syscon_setup_pll(struct lpc11u6x_syscon_regs *syscon, |
| uint32_t msel, uint32_t psel) |
| { |
| uint32_t val = msel & LPC11U6X_SYS_PLL_CTRL_MSEL_MASK; |
| |
| val |= (psel & LPC11U6X_SYS_PLL_CTRL_PSEL_MASK) << |
| LPC11U6X_SYS_PLL_CTRL_PSEL_SHIFT; |
| syscon->sys_pll_ctrl = val; |
| } |
| |
| static bool syscon_pll_locked(struct lpc11u6x_syscon_regs *syscon) |
| { |
| return (syscon->sys_pll_stat & 0x1) != 0; |
| } |
| |
| static void syscon_set_main_clock_source(struct lpc11u6x_syscon_regs *syscon, |
| uint32_t src) |
| { |
| syscon->main_clk_sel = src; |
| syscon->main_clk_uen = 0; |
| syscon->main_clk_uen = 1; |
| } |
| |
| static void syscon_ahb_clock_enable(struct lpc11u6x_syscon_regs *syscon, |
| uint32_t mask, bool enable) |
| { |
| if (enable) { |
| syscon->sys_ahb_clk_ctrl |= mask; |
| } else { |
| syscon->sys_ahb_clk_ctrl &= ~mask; |
| } |
| } |
| |
| static void syscon_peripheral_reset(struct lpc11u6x_syscon_regs *syscon, |
| uint32_t mask, bool reset) |
| { |
| if (reset) { |
| syscon->p_reset_ctrl &= ~mask; |
| } else { |
| syscon->p_reset_ctrl |= mask; |
| } |
| } |
| static void syscon_frg_init(struct lpc11u6x_syscon_regs *syscon) |
| { |
| uint32_t div; |
| |
| div = CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / LPC11U6X_USART_CLOCK_RATE; |
| if (!div) { |
| div = 1; |
| } |
| syscon->frg_clk_div = div; |
| |
| syscon_peripheral_reset(syscon, LPC11U6X_PRESET_CTRL_FRG, false); |
| syscon->uart_frg_div = 0xFF; |
| syscon->uart_frg_mult = ((CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / div) |
| * 256) / LPC11U6X_USART_CLOCK_RATE; |
| } |
| |
| static void syscon_frg_deinit(struct lpc11u6x_syscon_regs *syscon) |
| { |
| syscon->uart_frg_div = 0x0; |
| syscon_peripheral_reset(syscon, LPC11U6X_PRESET_CTRL_FRG, true); |
| } |
| |
| static int lpc11u6x_clock_control_on(const struct device *dev, |
| clock_control_subsys_t sub_system) |
| { |
| const struct lpc11u6x_syscon_config *cfg = dev->config; |
| struct lpc11u6x_syscon_data *data = dev->data; |
| uint32_t clk_mask = 0, reset_mask = 0; |
| int ret = 0, init_frg = 0; |
| |
| k_mutex_lock(&data->mutex, K_FOREVER); |
| |
| switch ((int) sub_system) { |
| case LPC11U6X_CLOCK_I2C0: |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_I2C0; |
| reset_mask = LPC11U6X_PRESET_CTRL_I2C0; |
| break; |
| case LPC11U6X_CLOCK_I2C1: |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_I2C1; |
| reset_mask = LPC11U6X_PRESET_CTRL_I2C1; |
| break; |
| case LPC11U6X_CLOCK_GPIO: |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_GPIO | |
| LPC11U6X_SYS_AHB_CLK_CTRL_PINT; |
| break; |
| case LPC11U6X_CLOCK_USART0: |
| cfg->syscon->usart0_clk_div = 1; |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART0; |
| break; |
| case LPC11U6X_CLOCK_USART1: |
| if (!data->frg_in_use++) { |
| init_frg = 1; |
| } |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART1; |
| reset_mask = LPC11U6X_PRESET_CTRL_USART1; |
| break; |
| case LPC11U6X_CLOCK_USART2: |
| if (!data->frg_in_use++) { |
| init_frg = 1; |
| } |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART2; |
| reset_mask = LPC11U6X_PRESET_CTRL_USART2; |
| break; |
| case LPC11U6X_CLOCK_USART3: |
| if (!data->frg_in_use++) { |
| init_frg = 1; |
| } |
| data->usart34_in_use++; |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART3_4; |
| reset_mask = LPC11U6X_PRESET_CTRL_USART3; |
| break; |
| case LPC11U6X_CLOCK_USART4: |
| if (!data->frg_in_use++) { |
| init_frg = 1; |
| } |
| data->usart34_in_use++; |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART3_4; |
| reset_mask = LPC11U6X_PRESET_CTRL_USART4; |
| break; |
| default: |
| k_mutex_unlock(&data->mutex); |
| return -EINVAL; |
| } |
| |
| syscon_ahb_clock_enable(cfg->syscon, clk_mask, true); |
| if (init_frg) { |
| syscon_frg_init(cfg->syscon); |
| } |
| syscon_peripheral_reset(cfg->syscon, reset_mask, false); |
| k_mutex_unlock(&data->mutex); |
| |
| return ret; |
| } |
| |
| static int lpc11u6x_clock_control_off(const struct device *dev, |
| clock_control_subsys_t sub_system) |
| { |
| const struct lpc11u6x_syscon_config *cfg = dev->config; |
| struct lpc11u6x_syscon_data *data = dev->data; |
| uint32_t clk_mask = 0, reset_mask = 0; |
| int ret = 0, deinit_frg = 0; |
| |
| k_mutex_lock(&data->mutex, K_FOREVER); |
| |
| switch ((int) sub_system) { |
| case LPC11U6X_CLOCK_I2C0: |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_I2C0; |
| reset_mask = LPC11U6X_PRESET_CTRL_I2C0; |
| break; |
| case LPC11U6X_CLOCK_I2C1: |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_I2C1; |
| reset_mask = LPC11U6X_PRESET_CTRL_I2C1; |
| break; |
| case LPC11U6X_CLOCK_GPIO: |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_GPIO | |
| LPC11U6X_SYS_AHB_CLK_CTRL_PINT; |
| break; |
| case LPC11U6X_CLOCK_USART0: |
| cfg->syscon->usart0_clk_div = 0; |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART0; |
| break; |
| case LPC11U6X_CLOCK_USART1: |
| if (!(--data->frg_in_use)) { |
| deinit_frg = 1; |
| } |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART1; |
| reset_mask = LPC11U6X_PRESET_CTRL_USART1; |
| break; |
| case LPC11U6X_CLOCK_USART2: |
| if (!(--data->frg_in_use)) { |
| deinit_frg = 1; |
| } |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART2; |
| reset_mask = LPC11U6X_PRESET_CTRL_USART2; |
| break; |
| case LPC11U6X_CLOCK_USART3: |
| if (!(--data->frg_in_use)) { |
| deinit_frg = 1; |
| } |
| if (!(--data->usart34_in_use)) { |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART3_4; |
| } |
| reset_mask = LPC11U6X_PRESET_CTRL_USART3; |
| break; |
| case LPC11U6X_CLOCK_USART4: |
| if (!(--data->frg_in_use)) { |
| deinit_frg = 1; |
| } |
| if (!(--data->usart34_in_use)) { |
| clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART3_4; |
| } |
| reset_mask = LPC11U6X_PRESET_CTRL_USART4; |
| break; |
| default: |
| k_mutex_unlock(&data->mutex); |
| return -EINVAL; |
| } |
| |
| syscon_ahb_clock_enable(cfg->syscon, clk_mask, false); |
| if (deinit_frg) { |
| syscon_frg_deinit(cfg->syscon); |
| } |
| syscon_peripheral_reset(cfg->syscon, reset_mask, true); |
| k_mutex_unlock(&data->mutex); |
| return ret; |
| |
| } |
| |
| static int lpc11u6x_clock_control_get_rate(const struct device *dev, |
| clock_control_subsys_t sub_system, |
| uint32_t *rate) |
| { |
| switch ((int) sub_system) { |
| case LPC11U6X_CLOCK_I2C0: |
| case LPC11U6X_CLOCK_I2C1: |
| case LPC11U6X_CLOCK_GPIO: |
| case LPC11U6X_CLOCK_USART0: |
| *rate = CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC; |
| break; |
| case LPC11U6X_CLOCK_USART1: |
| case LPC11U6X_CLOCK_USART2: |
| case LPC11U6X_CLOCK_USART3: |
| case LPC11U6X_CLOCK_USART4: |
| *rate = LPC11U6X_USART_CLOCK_RATE; |
| break; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int lpc11u6x_syscon_init(const struct device *dev) |
| { |
| const struct lpc11u6x_syscon_config *cfg = dev->config; |
| struct lpc11u6x_syscon_data *data = dev->data; |
| uint32_t val; |
| |
| k_mutex_init(&data->mutex); |
| data->frg_in_use = 0; |
| data->usart34_in_use = 0; |
| /* Enable SRAM1 and USB ram if needed */ |
| val = 0; |
| #ifdef CONFIG_CLOCK_CONTROL_LPC11U6X_ENABLE_SRAM1 |
| val |= LPC11U6X_SYS_AHB_CLK_CTRL_SRAM1; |
| #endif /* CONFIG_CLOCK_CONTROL_LPC11U6X_ENABLE_SRAM1 */ |
| #ifdef CONFIG_CLOCK_CONTROL_LPC11U6X_ENABLE_USB_RAM |
| val |= LPC11U6X_SYS_AHB_CLK_CTRL_USB_SRAM; |
| #endif /* CONFIG_CLOCK_CONTROL_LPC11U6X_ENABLE_USB_RAM */ |
| |
| /* Enable IOCON (I/O Control) clock. */ |
| val |= LPC11U6X_SYS_AHB_CLK_CTRL_IOCON; |
| |
| syscon_ahb_clock_enable(cfg->syscon, val, true); |
| |
| /* Configure PLL output as the main clock source, with a frequency of |
| * 48MHz |
| */ |
| #ifdef CONFIG_CLOCK_CONTROL_LPC11U6X_PLL_SRC_SYSOSC |
| syscon_power_up(cfg->syscon, LPC11U6X_PDRUNCFG_SYSOSC_PD, true); |
| |
| /* Wait ~500us */ |
| for (int i = 0; i < 2500; i++) { |
| } |
| |
| /* Configure PLL input */ |
| syscon_set_pll_src(cfg->syscon, LPC11U6X_SYS_PLL_CLK_SEL_SYSOSC); |
| |
| pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT); |
| |
| #elif defined(CONFIG_CLOCK_CONTROL_LPC11U6X_PLL_SRC_IRC) |
| syscon_power_up(cfg->syscon, LPC11U6X_PDRUNCFG_IRC_PD, true); |
| syscon_set_pll_src(cfg->syscon, LPC11U6X_SYS_PLL_CLK_SEL_IRC); |
| #endif |
| /* Flash access takes 3 clock cycles for main clock frequencies |
| * between 40MHz and 50MHz |
| */ |
| set_flash_access_time(LPC11U6X_FLASH_TIMING_3CYCLES); |
| |
| /* Shutdown PLL to change divider/mult ratios */ |
| syscon_power_up(cfg->syscon, LPC11U6X_PDRUNCFG_PLL_PD, false); |
| |
| /* Setup PLL to have 48MHz output */ |
| syscon_setup_pll(cfg->syscon, 3, 1); |
| |
| /* Power up pll and wait */ |
| syscon_power_up(cfg->syscon, LPC11U6X_PDRUNCFG_PLL_PD, true); |
| |
| while (!syscon_pll_locked(cfg->syscon)) { |
| } |
| |
| cfg->syscon->sys_ahb_clk_div = 1; |
| syscon_set_main_clock_source(cfg->syscon, LPC11U6X_MAIN_CLK_SRC_PLLOUT); |
| return 0; |
| } |
| |
| static const struct clock_control_driver_api lpc11u6x_clock_control_api = { |
| .on = lpc11u6x_clock_control_on, |
| .off = lpc11u6x_clock_control_off, |
| .get_rate = lpc11u6x_clock_control_get_rate, |
| }; |
| |
| |
| PINCTRL_DT_INST_DEFINE(0); |
| |
| static const struct lpc11u6x_syscon_config syscon_config = { |
| .syscon = (struct lpc11u6x_syscon_regs *) DT_INST_REG_ADDR(0), |
| .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), |
| }; |
| |
| static struct lpc11u6x_syscon_data syscon_data; |
| |
| DEVICE_DT_INST_DEFINE(0, |
| lpc11u6x_syscon_init, |
| NULL, |
| &syscon_data, &syscon_config, |
| PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY, |
| &lpc11u6x_clock_control_api); |