You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
9.3 KiB
362 lines
9.3 KiB
/* |
|
* 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 DEVICE_API(clock_control, 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);
|
|
|