401 lines
10 KiB
C
401 lines
10 KiB
C
/*
|
|
* Copyright (c) 2020, Seagate Technology LLC
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nxp_lpc11u6x_syscon
|
|
|
|
#include <devicetree.h>
|
|
#include <device.h>
|
|
|
|
#include <drivers/clock_control/lpc11u6x_clock_control.h>
|
|
#include <drivers/pinmux.h>
|
|
|
|
#include "clock_control_lpc11u6x.h"
|
|
|
|
#define DEV_CFG(dev) ((const struct lpc11u6x_syscon_config *) \
|
|
((dev)->config_info))
|
|
|
|
#define DEV_DATA(dev) ((struct lpc11u6x_syscon_data *) \
|
|
((dev)->driver_data))
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_CLOCK_CONTROL_LPC11U6X_PLL_SRC_SYSOSC) \
|
|
&& DT_INST_NODE_HAS_PROP(0, pinmuxs)
|
|
/**
|
|
* @brief: configure system oscillator pins.
|
|
*
|
|
* This system oscillator pins and their configurations are retrieved from the
|
|
* "pinmuxs" property of the DT clock controller node.
|
|
*/
|
|
static void pinmux_enable_sysosc(void)
|
|
{
|
|
struct device *pinmux_dev;
|
|
uint32_t pin, func;
|
|
|
|
pinmux_dev = device_get_binding(
|
|
DT_LABEL(DT_INST_PHANDLE_BY_NAME(0, pinmuxs, xtalin)));
|
|
if (!pinmux_dev) {
|
|
return;
|
|
}
|
|
pin = DT_INST_PHA_BY_NAME(0, pinmuxs, xtalin, pin);
|
|
func = DT_INST_PHA_BY_NAME(0, pinmuxs, xtalin, function);
|
|
|
|
pinmux_pin_set(pinmux_dev, pin, func);
|
|
|
|
pinmux_dev = device_get_binding(
|
|
DT_LABEL(DT_INST_PHANDLE_BY_NAME(0, pinmuxs, xtalout)));
|
|
if (!pinmux_dev) {
|
|
return;
|
|
}
|
|
pin = DT_INST_PHA_BY_NAME(0, pinmuxs, xtalout, pin);
|
|
func = DT_INST_PHA_BY_NAME(0, pinmuxs, xtalout, function);
|
|
|
|
pinmux_pin_set(pinmux_dev, pin, func);
|
|
}
|
|
#else
|
|
#define pinmux_enable_sysosc() do { } while (0)
|
|
#endif
|
|
|
|
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(struct device *dev,
|
|
clock_control_subsys_t sub_system)
|
|
{
|
|
const struct lpc11u6x_syscon_config *cfg = DEV_CFG(dev);
|
|
struct lpc11u6x_syscon_data *data = DEV_DATA(dev);
|
|
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(struct device *dev,
|
|
clock_control_subsys_t sub_system)
|
|
{
|
|
const struct lpc11u6x_syscon_config *cfg = DEV_CFG(dev);
|
|
struct lpc11u6x_syscon_data *data = DEV_DATA(dev);
|
|
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(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(struct device *dev)
|
|
{
|
|
const struct lpc11u6x_syscon_config *cfg = DEV_CFG(dev);
|
|
struct lpc11u6x_syscon_data *data = DEV_DATA(dev);
|
|
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);
|
|
|
|
pinmux_enable_sysosc();
|
|
|
|
#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,
|
|
};
|
|
|
|
static const struct lpc11u6x_syscon_config syscon_config = {
|
|
.syscon = (struct lpc11u6x_syscon_regs *) DT_INST_REG_ADDR(0),
|
|
};
|
|
|
|
static struct lpc11u6x_syscon_data syscon_data;
|
|
|
|
DEVICE_AND_API_INIT(lpc11u6x_syscon, DT_INST_LABEL(0),
|
|
&lpc11u6x_syscon_init,
|
|
&syscon_data, &syscon_config,
|
|
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS,
|
|
&lpc11u6x_clock_control_api);
|