| /* |
| * Copyright (c) 2020 IoT.bzh |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT renesas_rcar_cpg_mssr |
| #include <errno.h> |
| #include <soc.h> |
| #include <drivers/clock_control.h> |
| #include <drivers/clock_control/rcar_clock_control.h> |
| #include <dt-bindings/clock/renesas_rcar_cpg.h> |
| |
| struct rcar_mssr_config { |
| uint32_t base_address; |
| }; |
| |
| static const uint16_t rmstpsr[] = { |
| 0x110, 0x114, 0x118, 0x11c, |
| 0x120, 0x124, 0x128, 0x12c, |
| 0x980, 0x984, 0x988, 0x98c, |
| }; |
| |
| #define RMSTPSR(i) rmstpsr[i] |
| |
| /* Software Reset Register offsets */ |
| static const uint16_t srcr[] = { |
| 0x0A0, 0x0A8, 0x0B0, 0x0B8, |
| 0x0BC, 0x0C4, 0x1C8, 0x1CC, |
| 0x920, 0x924, 0x928, 0x92C, |
| }; |
| |
| #define SRCR(i) srcr[i] |
| #define SRSTCLR(i) (0x940 + (i) * 4) |
| |
| /* CPG write protect */ |
| #define CPGWPR 0x0900 |
| /* CAN-FD Clock Frequency Control Register */ |
| #define CANFDCKCR 0x244 |
| |
| /* Clock stop bit */ |
| #define CANFDCKCR_CKSTP BIT(8) |
| |
| /* On H3,M3,E3 parent clock of CANFD has 800MHz rate */ |
| #define CANFDCKCR_PARENT_CLK_RATE 800000000 |
| #define CANFDCKCR_DIVIDER_MASK 0x1FF |
| |
| #define S3D4_CLK_RATE 66600000 |
| |
| static void cpg_write(const struct rcar_mssr_config *config, |
| uint32_t reg, uint32_t val) |
| { |
| sys_write32(~val, config->base_address + CPGWPR); |
| sys_write32(val, config->base_address + reg); |
| /* Wait for at least one cycle of the RCLK clock (@ ca. 32 kHz) */ |
| k_sleep(K_USEC(35)); |
| } |
| |
| static void cpg_reset(const struct rcar_mssr_config *config, |
| uint32_t reg, uint32_t bit) |
| { |
| cpg_write(config, SRCR(reg), BIT(bit)); |
| cpg_write(config, SRSTCLR(reg), BIT(bit)); |
| } |
| |
| static int cpg_core_clock_endisable(const struct device *dev, |
| uint32_t module, uint32_t rate, bool enable) |
| { |
| const struct rcar_mssr_config *config = dev->config; |
| uint32_t divider; |
| unsigned int key; |
| int ret; |
| |
| /* Only support CANFD core clock at the moment */ |
| if (module != CPG_CORE_CLK_CANFD) { |
| return -EINVAL; |
| } |
| |
| key = irq_lock(); |
| |
| if (enable) { |
| if ((CANFDCKCR_PARENT_CLK_RATE % rate) != 0) { |
| __ASSERT(true, "Can not generate " |
| "%u from CANFD parent clock", rate); |
| ret = -EINVAL; |
| goto unlock; |
| } |
| divider = (CANFDCKCR_PARENT_CLK_RATE / rate) - 1; |
| if (divider > CANFDCKCR_DIVIDER_MASK) { |
| __ASSERT(true, "Can not generate %u " |
| "from CANFD parent clock", rate); |
| ret = -EINVAL; |
| goto unlock; |
| } |
| cpg_write(config, CANFDCKCR, divider); |
| } else { |
| cpg_write(config, CANFDCKCR, CANFDCKCR_CKSTP); |
| } |
| |
| unlock: |
| irq_unlock(key); |
| return 0; |
| } |
| |
| static int cpg_rmstp_clock_endisable(const struct device *dev, |
| uint32_t module, bool enable) |
| { |
| const struct rcar_mssr_config *config = dev->config; |
| uint32_t reg = module / 100; |
| uint32_t bit = module % 100; |
| uint32_t bitmask = BIT(bit); |
| uint32_t reg_val; |
| |
| __ASSERT((bit < 32) && reg < ARRAY_SIZE(rmstpsr), |
| "Invalid module number for cpg clock: %d", module); |
| |
| unsigned int key = irq_lock(); |
| |
| reg_val = sys_read32(config->base_address + RMSTPSR(reg)); |
| if (enable) { |
| reg_val &= ~bitmask; |
| } else { |
| reg_val |= bitmask; |
| } |
| |
| sys_write32(reg_val, config->base_address + RMSTPSR(reg)); |
| if (!enable) { |
| cpg_reset(config, reg, bit); |
| } |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int cpg_mssr_blocking_start(const struct device *dev, |
| clock_control_subsys_t sys) |
| { |
| struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys; |
| int ret = -EINVAL; |
| |
| if (clk->domain == CPG_MOD) { |
| ret = cpg_rmstp_clock_endisable(dev, clk->module, true); |
| } else if (clk->domain == CPG_CORE) { |
| ret = cpg_core_clock_endisable(dev, clk->module, clk->rate, |
| true); |
| } |
| |
| return ret; |
| } |
| |
| static int cpg_mssr_stop(const struct device *dev, |
| clock_control_subsys_t sys) |
| { |
| struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys; |
| int ret = -EINVAL; |
| |
| if (clk->domain == CPG_MOD) { |
| ret = cpg_rmstp_clock_endisable(dev, clk->module, false); |
| } else if (clk->domain == CPG_CORE) { |
| ret = cpg_core_clock_endisable(dev, clk->module, 0, false); |
| } |
| |
| return ret; |
| } |
| |
| static int cpg_get_rate(const struct device *dev, |
| clock_control_subsys_t sys, |
| uint32_t *rate) |
| { |
| const struct rcar_mssr_config *config = dev->config; |
| struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys; |
| uint32_t val; |
| int ret = 0; |
| |
| if (clk->domain != CPG_CORE) { |
| return -ENOTSUP; |
| } |
| |
| switch (clk->module) { |
| case CPG_CORE_CLK_CANFD: |
| val = sys_read32(config->base_address + CANFDCKCR); |
| if (val & CANFDCKCR_CKSTP) { |
| *rate = 0; |
| } else { |
| val &= CANFDCKCR_DIVIDER_MASK; |
| *rate = CANFDCKCR_PARENT_CLK_RATE / (val + 1); |
| } |
| break; |
| case CPG_CORE_CLK_S3D4: |
| *rate = S3D4_CLK_RATE; |
| break; |
| default: |
| ret = -ENOTSUP; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int rcar_cpg_mssr_init(const struct device *dev) |
| { |
| return 0; |
| } |
| |
| static const struct clock_control_driver_api rcar_cpg_mssr_api = { |
| .on = cpg_mssr_blocking_start, |
| .off = cpg_mssr_stop, |
| .get_rate = cpg_get_rate, |
| }; |
| |
| #define RCAR_MSSR_INIT(inst) \ |
| static struct rcar_mssr_config rcar_mssr##inst##_config = { \ |
| .base_address = DT_INST_REG_ADDR(inst) \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(inst, \ |
| &rcar_cpg_mssr_init, \ |
| NULL, \ |
| NULL, &rcar_mssr##inst##_config, \ |
| PRE_KERNEL_1, \ |
| CONFIG_CLOCK_CONTROL_INIT_PRIORITY, \ |
| &rcar_cpg_mssr_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(RCAR_MSSR_INIT) |