162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Marvell Armada 37xx SoC Peripheral clocks 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2016 Marvell 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Gregory CLEMENT <gregory.clement@free-electrons.com> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Most of the peripheral clocks can be modelled like this: 1062306a36Sopenharmony_ci * _____ _______ _______ 1162306a36Sopenharmony_ci * TBG-A-P --| | | | | | ______ 1262306a36Sopenharmony_ci * TBG-B-P --| Mux |--| /div1 |--| /div2 |--| Gate |--> perip_clk 1362306a36Sopenharmony_ci * TBG-A-S --| | | | | | |______| 1462306a36Sopenharmony_ci * TBG-B-S --|_____| |_______| |_______| 1562306a36Sopenharmony_ci * 1662306a36Sopenharmony_ci * However some clocks may use only one or two block or and use the 1762306a36Sopenharmony_ci * xtal clock as parent. 1862306a36Sopenharmony_ci */ 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <linux/clk-provider.h> 2162306a36Sopenharmony_ci#include <linux/io.h> 2262306a36Sopenharmony_ci#include <linux/mfd/syscon.h> 2362306a36Sopenharmony_ci#include <linux/of.h> 2462306a36Sopenharmony_ci#include <linux/platform_device.h> 2562306a36Sopenharmony_ci#include <linux/regmap.h> 2662306a36Sopenharmony_ci#include <linux/slab.h> 2762306a36Sopenharmony_ci#include <linux/jiffies.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define TBG_SEL 0x0 3062306a36Sopenharmony_ci#define DIV_SEL0 0x4 3162306a36Sopenharmony_ci#define DIV_SEL1 0x8 3262306a36Sopenharmony_ci#define DIV_SEL2 0xC 3362306a36Sopenharmony_ci#define CLK_SEL 0x10 3462306a36Sopenharmony_ci#define CLK_DIS 0x14 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define ARMADA_37XX_DVFS_LOAD_1 1 3762306a36Sopenharmony_ci#define LOAD_LEVEL_NR 4 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define ARMADA_37XX_NB_L0L1 0x18 4062306a36Sopenharmony_ci#define ARMADA_37XX_NB_L2L3 0x1C 4162306a36Sopenharmony_ci#define ARMADA_37XX_NB_TBG_DIV_OFF 13 4262306a36Sopenharmony_ci#define ARMADA_37XX_NB_TBG_DIV_MASK 0x7 4362306a36Sopenharmony_ci#define ARMADA_37XX_NB_CLK_SEL_OFF 11 4462306a36Sopenharmony_ci#define ARMADA_37XX_NB_CLK_SEL_MASK 0x1 4562306a36Sopenharmony_ci#define ARMADA_37XX_NB_TBG_SEL_OFF 9 4662306a36Sopenharmony_ci#define ARMADA_37XX_NB_TBG_SEL_MASK 0x3 4762306a36Sopenharmony_ci#define ARMADA_37XX_NB_CONFIG_SHIFT 16 4862306a36Sopenharmony_ci#define ARMADA_37XX_NB_DYN_MOD 0x24 4962306a36Sopenharmony_ci#define ARMADA_37XX_NB_DFS_EN 31 5062306a36Sopenharmony_ci#define ARMADA_37XX_NB_CPU_LOAD 0x30 5162306a36Sopenharmony_ci#define ARMADA_37XX_NB_CPU_LOAD_MASK 0x3 5262306a36Sopenharmony_ci#define ARMADA_37XX_DVFS_LOAD_0 0 5362306a36Sopenharmony_ci#define ARMADA_37XX_DVFS_LOAD_1 1 5462306a36Sopenharmony_ci#define ARMADA_37XX_DVFS_LOAD_2 2 5562306a36Sopenharmony_ci#define ARMADA_37XX_DVFS_LOAD_3 3 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistruct clk_periph_driver_data { 5862306a36Sopenharmony_ci struct clk_hw_onecell_data *hw_data; 5962306a36Sopenharmony_ci spinlock_t lock; 6062306a36Sopenharmony_ci void __iomem *reg; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci /* Storage registers for suspend/resume operations */ 6362306a36Sopenharmony_ci u32 tbg_sel; 6462306a36Sopenharmony_ci u32 div_sel0; 6562306a36Sopenharmony_ci u32 div_sel1; 6662306a36Sopenharmony_ci u32 div_sel2; 6762306a36Sopenharmony_ci u32 clk_sel; 6862306a36Sopenharmony_ci u32 clk_dis; 6962306a36Sopenharmony_ci}; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistruct clk_double_div { 7262306a36Sopenharmony_ci struct clk_hw hw; 7362306a36Sopenharmony_ci void __iomem *reg1; 7462306a36Sopenharmony_ci u8 shift1; 7562306a36Sopenharmony_ci void __iomem *reg2; 7662306a36Sopenharmony_ci u8 shift2; 7762306a36Sopenharmony_ci}; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistruct clk_pm_cpu { 8062306a36Sopenharmony_ci struct clk_hw hw; 8162306a36Sopenharmony_ci void __iomem *reg_mux; 8262306a36Sopenharmony_ci u8 shift_mux; 8362306a36Sopenharmony_ci u32 mask_mux; 8462306a36Sopenharmony_ci void __iomem *reg_div; 8562306a36Sopenharmony_ci u8 shift_div; 8662306a36Sopenharmony_ci struct regmap *nb_pm_base; 8762306a36Sopenharmony_ci unsigned long l1_expiration; 8862306a36Sopenharmony_ci}; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci#define to_clk_double_div(_hw) container_of(_hw, struct clk_double_div, hw) 9162306a36Sopenharmony_ci#define to_clk_pm_cpu(_hw) container_of(_hw, struct clk_pm_cpu, hw) 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistruct clk_periph_data { 9462306a36Sopenharmony_ci const char *name; 9562306a36Sopenharmony_ci const char * const *parent_names; 9662306a36Sopenharmony_ci int num_parents; 9762306a36Sopenharmony_ci struct clk_hw *mux_hw; 9862306a36Sopenharmony_ci struct clk_hw *rate_hw; 9962306a36Sopenharmony_ci struct clk_hw *gate_hw; 10062306a36Sopenharmony_ci struct clk_hw *muxrate_hw; 10162306a36Sopenharmony_ci bool is_double_div; 10262306a36Sopenharmony_ci}; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic const struct clk_div_table clk_table6[] = { 10562306a36Sopenharmony_ci { .val = 1, .div = 1, }, 10662306a36Sopenharmony_ci { .val = 2, .div = 2, }, 10762306a36Sopenharmony_ci { .val = 3, .div = 3, }, 10862306a36Sopenharmony_ci { .val = 4, .div = 4, }, 10962306a36Sopenharmony_ci { .val = 5, .div = 5, }, 11062306a36Sopenharmony_ci { .val = 6, .div = 6, }, 11162306a36Sopenharmony_ci { .val = 0, .div = 0, }, /* last entry */ 11262306a36Sopenharmony_ci}; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic const struct clk_div_table clk_table1[] = { 11562306a36Sopenharmony_ci { .val = 0, .div = 1, }, 11662306a36Sopenharmony_ci { .val = 1, .div = 2, }, 11762306a36Sopenharmony_ci { .val = 0, .div = 0, }, /* last entry */ 11862306a36Sopenharmony_ci}; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic const struct clk_div_table clk_table2[] = { 12162306a36Sopenharmony_ci { .val = 0, .div = 2, }, 12262306a36Sopenharmony_ci { .val = 1, .div = 4, }, 12362306a36Sopenharmony_ci { .val = 0, .div = 0, }, /* last entry */ 12462306a36Sopenharmony_ci}; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic const struct clk_ops clk_double_div_ops; 12762306a36Sopenharmony_cistatic const struct clk_ops clk_pm_cpu_ops; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci#define PERIPH_GATE(_name, _bit) \ 13062306a36Sopenharmony_cistruct clk_gate gate_##_name = { \ 13162306a36Sopenharmony_ci .reg = (void *)CLK_DIS, \ 13262306a36Sopenharmony_ci .bit_idx = _bit, \ 13362306a36Sopenharmony_ci .hw.init = &(struct clk_init_data){ \ 13462306a36Sopenharmony_ci .ops = &clk_gate_ops, \ 13562306a36Sopenharmony_ci } \ 13662306a36Sopenharmony_ci}; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci#define PERIPH_MUX(_name, _shift) \ 13962306a36Sopenharmony_cistruct clk_mux mux_##_name = { \ 14062306a36Sopenharmony_ci .reg = (void *)TBG_SEL, \ 14162306a36Sopenharmony_ci .shift = _shift, \ 14262306a36Sopenharmony_ci .mask = 3, \ 14362306a36Sopenharmony_ci .hw.init = &(struct clk_init_data){ \ 14462306a36Sopenharmony_ci .ops = &clk_mux_ro_ops, \ 14562306a36Sopenharmony_ci } \ 14662306a36Sopenharmony_ci}; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci#define PERIPH_DOUBLEDIV(_name, _reg1, _reg2, _shift1, _shift2) \ 14962306a36Sopenharmony_cistruct clk_double_div rate_##_name = { \ 15062306a36Sopenharmony_ci .reg1 = (void *)_reg1, \ 15162306a36Sopenharmony_ci .reg2 = (void *)_reg2, \ 15262306a36Sopenharmony_ci .shift1 = _shift1, \ 15362306a36Sopenharmony_ci .shift2 = _shift2, \ 15462306a36Sopenharmony_ci .hw.init = &(struct clk_init_data){ \ 15562306a36Sopenharmony_ci .ops = &clk_double_div_ops, \ 15662306a36Sopenharmony_ci } \ 15762306a36Sopenharmony_ci}; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci#define PERIPH_DIV(_name, _reg, _shift, _table) \ 16062306a36Sopenharmony_cistruct clk_divider rate_##_name = { \ 16162306a36Sopenharmony_ci .reg = (void *)_reg, \ 16262306a36Sopenharmony_ci .table = _table, \ 16362306a36Sopenharmony_ci .shift = _shift, \ 16462306a36Sopenharmony_ci .hw.init = &(struct clk_init_data){ \ 16562306a36Sopenharmony_ci .ops = &clk_divider_ro_ops, \ 16662306a36Sopenharmony_ci } \ 16762306a36Sopenharmony_ci}; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci#define PERIPH_PM_CPU(_name, _shift1, _reg, _shift2) \ 17062306a36Sopenharmony_cistruct clk_pm_cpu muxrate_##_name = { \ 17162306a36Sopenharmony_ci .reg_mux = (void *)TBG_SEL, \ 17262306a36Sopenharmony_ci .mask_mux = 3, \ 17362306a36Sopenharmony_ci .shift_mux = _shift1, \ 17462306a36Sopenharmony_ci .reg_div = (void *)_reg, \ 17562306a36Sopenharmony_ci .shift_div = _shift2, \ 17662306a36Sopenharmony_ci .hw.init = &(struct clk_init_data){ \ 17762306a36Sopenharmony_ci .ops = &clk_pm_cpu_ops, \ 17862306a36Sopenharmony_ci } \ 17962306a36Sopenharmony_ci}; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci#define PERIPH_CLK_FULL_DD(_name, _bit, _shift, _reg1, _reg2, _shift1, _shift2)\ 18262306a36Sopenharmony_cistatic PERIPH_GATE(_name, _bit); \ 18362306a36Sopenharmony_cistatic PERIPH_MUX(_name, _shift); \ 18462306a36Sopenharmony_cistatic PERIPH_DOUBLEDIV(_name, _reg1, _reg2, _shift1, _shift2); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci#define PERIPH_CLK_FULL(_name, _bit, _shift, _reg, _shift1, _table) \ 18762306a36Sopenharmony_cistatic PERIPH_GATE(_name, _bit); \ 18862306a36Sopenharmony_cistatic PERIPH_MUX(_name, _shift); \ 18962306a36Sopenharmony_cistatic PERIPH_DIV(_name, _reg, _shift1, _table); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci#define PERIPH_CLK_GATE_DIV(_name, _bit, _reg, _shift, _table) \ 19262306a36Sopenharmony_cistatic PERIPH_GATE(_name, _bit); \ 19362306a36Sopenharmony_cistatic PERIPH_DIV(_name, _reg, _shift, _table); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci#define PERIPH_CLK_MUX_DD(_name, _shift, _reg1, _reg2, _shift1, _shift2)\ 19662306a36Sopenharmony_cistatic PERIPH_MUX(_name, _shift); \ 19762306a36Sopenharmony_cistatic PERIPH_DOUBLEDIV(_name, _reg1, _reg2, _shift1, _shift2); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci#define REF_CLK_FULL(_name) \ 20062306a36Sopenharmony_ci { .name = #_name, \ 20162306a36Sopenharmony_ci .parent_names = (const char *[]){ "TBG-A-P", \ 20262306a36Sopenharmony_ci "TBG-B-P", "TBG-A-S", "TBG-B-S"}, \ 20362306a36Sopenharmony_ci .num_parents = 4, \ 20462306a36Sopenharmony_ci .mux_hw = &mux_##_name.hw, \ 20562306a36Sopenharmony_ci .gate_hw = &gate_##_name.hw, \ 20662306a36Sopenharmony_ci .rate_hw = &rate_##_name.hw, \ 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci#define REF_CLK_FULL_DD(_name) \ 21062306a36Sopenharmony_ci { .name = #_name, \ 21162306a36Sopenharmony_ci .parent_names = (const char *[]){ "TBG-A-P", \ 21262306a36Sopenharmony_ci "TBG-B-P", "TBG-A-S", "TBG-B-S"}, \ 21362306a36Sopenharmony_ci .num_parents = 4, \ 21462306a36Sopenharmony_ci .mux_hw = &mux_##_name.hw, \ 21562306a36Sopenharmony_ci .gate_hw = &gate_##_name.hw, \ 21662306a36Sopenharmony_ci .rate_hw = &rate_##_name.hw, \ 21762306a36Sopenharmony_ci .is_double_div = true, \ 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci#define REF_CLK_GATE(_name, _parent_name) \ 22162306a36Sopenharmony_ci { .name = #_name, \ 22262306a36Sopenharmony_ci .parent_names = (const char *[]){ _parent_name}, \ 22362306a36Sopenharmony_ci .num_parents = 1, \ 22462306a36Sopenharmony_ci .gate_hw = &gate_##_name.hw, \ 22562306a36Sopenharmony_ci } 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci#define REF_CLK_GATE_DIV(_name, _parent_name) \ 22862306a36Sopenharmony_ci { .name = #_name, \ 22962306a36Sopenharmony_ci .parent_names = (const char *[]){ _parent_name}, \ 23062306a36Sopenharmony_ci .num_parents = 1, \ 23162306a36Sopenharmony_ci .gate_hw = &gate_##_name.hw, \ 23262306a36Sopenharmony_ci .rate_hw = &rate_##_name.hw, \ 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci#define REF_CLK_PM_CPU(_name) \ 23662306a36Sopenharmony_ci { .name = #_name, \ 23762306a36Sopenharmony_ci .parent_names = (const char *[]){ "TBG-A-P", \ 23862306a36Sopenharmony_ci "TBG-B-P", "TBG-A-S", "TBG-B-S"}, \ 23962306a36Sopenharmony_ci .num_parents = 4, \ 24062306a36Sopenharmony_ci .muxrate_hw = &muxrate_##_name.hw, \ 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci#define REF_CLK_MUX_DD(_name) \ 24462306a36Sopenharmony_ci { .name = #_name, \ 24562306a36Sopenharmony_ci .parent_names = (const char *[]){ "TBG-A-P", \ 24662306a36Sopenharmony_ci "TBG-B-P", "TBG-A-S", "TBG-B-S"}, \ 24762306a36Sopenharmony_ci .num_parents = 4, \ 24862306a36Sopenharmony_ci .mux_hw = &mux_##_name.hw, \ 24962306a36Sopenharmony_ci .rate_hw = &rate_##_name.hw, \ 25062306a36Sopenharmony_ci .is_double_div = true, \ 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci/* NB periph clocks */ 25462306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(mmc, 2, 0, DIV_SEL2, DIV_SEL2, 16, 13); 25562306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(sata_host, 3, 2, DIV_SEL2, DIV_SEL2, 10, 7); 25662306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(sec_at, 6, 4, DIV_SEL1, DIV_SEL1, 3, 0); 25762306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(sec_dap, 7, 6, DIV_SEL1, DIV_SEL1, 9, 6); 25862306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(tscem, 8, 8, DIV_SEL1, DIV_SEL1, 15, 12); 25962306a36Sopenharmony_ciPERIPH_CLK_FULL(tscem_tmx, 10, 10, DIV_SEL1, 18, clk_table6); 26062306a36Sopenharmony_cistatic PERIPH_GATE(avs, 11); 26162306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(pwm, 13, 14, DIV_SEL0, DIV_SEL0, 3, 0); 26262306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(sqf, 12, 12, DIV_SEL1, DIV_SEL1, 27, 24); 26362306a36Sopenharmony_cistatic PERIPH_GATE(i2c_2, 16); 26462306a36Sopenharmony_cistatic PERIPH_GATE(i2c_1, 17); 26562306a36Sopenharmony_ciPERIPH_CLK_GATE_DIV(ddr_phy, 19, DIV_SEL0, 18, clk_table2); 26662306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(ddr_fclk, 21, 16, DIV_SEL0, DIV_SEL0, 15, 12); 26762306a36Sopenharmony_ciPERIPH_CLK_FULL(trace, 22, 18, DIV_SEL0, 20, clk_table6); 26862306a36Sopenharmony_ciPERIPH_CLK_FULL(counter, 23, 20, DIV_SEL0, 23, clk_table6); 26962306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(eip97, 24, 24, DIV_SEL2, DIV_SEL2, 22, 19); 27062306a36Sopenharmony_cistatic PERIPH_PM_CPU(cpu, 22, DIV_SEL0, 28); 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic struct clk_periph_data data_nb[] = { 27362306a36Sopenharmony_ci REF_CLK_FULL_DD(mmc), 27462306a36Sopenharmony_ci REF_CLK_FULL_DD(sata_host), 27562306a36Sopenharmony_ci REF_CLK_FULL_DD(sec_at), 27662306a36Sopenharmony_ci REF_CLK_FULL_DD(sec_dap), 27762306a36Sopenharmony_ci REF_CLK_FULL_DD(tscem), 27862306a36Sopenharmony_ci REF_CLK_FULL(tscem_tmx), 27962306a36Sopenharmony_ci REF_CLK_GATE(avs, "xtal"), 28062306a36Sopenharmony_ci REF_CLK_FULL_DD(sqf), 28162306a36Sopenharmony_ci REF_CLK_FULL_DD(pwm), 28262306a36Sopenharmony_ci REF_CLK_GATE(i2c_2, "xtal"), 28362306a36Sopenharmony_ci REF_CLK_GATE(i2c_1, "xtal"), 28462306a36Sopenharmony_ci REF_CLK_GATE_DIV(ddr_phy, "TBG-A-S"), 28562306a36Sopenharmony_ci REF_CLK_FULL_DD(ddr_fclk), 28662306a36Sopenharmony_ci REF_CLK_FULL(trace), 28762306a36Sopenharmony_ci REF_CLK_FULL(counter), 28862306a36Sopenharmony_ci REF_CLK_FULL_DD(eip97), 28962306a36Sopenharmony_ci REF_CLK_PM_CPU(cpu), 29062306a36Sopenharmony_ci { }, 29162306a36Sopenharmony_ci}; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci/* SB periph clocks */ 29462306a36Sopenharmony_ciPERIPH_CLK_MUX_DD(gbe_50, 6, DIV_SEL2, DIV_SEL2, 6, 9); 29562306a36Sopenharmony_ciPERIPH_CLK_MUX_DD(gbe_core, 8, DIV_SEL1, DIV_SEL1, 18, 21); 29662306a36Sopenharmony_ciPERIPH_CLK_MUX_DD(gbe_125, 10, DIV_SEL1, DIV_SEL1, 6, 9); 29762306a36Sopenharmony_cistatic PERIPH_GATE(gbe1_50, 0); 29862306a36Sopenharmony_cistatic PERIPH_GATE(gbe0_50, 1); 29962306a36Sopenharmony_cistatic PERIPH_GATE(gbe1_125, 2); 30062306a36Sopenharmony_cistatic PERIPH_GATE(gbe0_125, 3); 30162306a36Sopenharmony_ciPERIPH_CLK_GATE_DIV(gbe1_core, 4, DIV_SEL1, 13, clk_table1); 30262306a36Sopenharmony_ciPERIPH_CLK_GATE_DIV(gbe0_core, 5, DIV_SEL1, 14, clk_table1); 30362306a36Sopenharmony_ciPERIPH_CLK_GATE_DIV(gbe_bm, 12, DIV_SEL1, 0, clk_table1); 30462306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(sdio, 11, 14, DIV_SEL0, DIV_SEL0, 3, 6); 30562306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(usb32_usb2_sys, 16, 16, DIV_SEL0, DIV_SEL0, 9, 12); 30662306a36Sopenharmony_ciPERIPH_CLK_FULL_DD(usb32_ss_sys, 17, 18, DIV_SEL0, DIV_SEL0, 15, 18); 30762306a36Sopenharmony_cistatic PERIPH_GATE(pcie, 14); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_cistatic struct clk_periph_data data_sb[] = { 31062306a36Sopenharmony_ci REF_CLK_MUX_DD(gbe_50), 31162306a36Sopenharmony_ci REF_CLK_MUX_DD(gbe_core), 31262306a36Sopenharmony_ci REF_CLK_MUX_DD(gbe_125), 31362306a36Sopenharmony_ci REF_CLK_GATE(gbe1_50, "gbe_50"), 31462306a36Sopenharmony_ci REF_CLK_GATE(gbe0_50, "gbe_50"), 31562306a36Sopenharmony_ci REF_CLK_GATE(gbe1_125, "gbe_125"), 31662306a36Sopenharmony_ci REF_CLK_GATE(gbe0_125, "gbe_125"), 31762306a36Sopenharmony_ci REF_CLK_GATE_DIV(gbe1_core, "gbe_core"), 31862306a36Sopenharmony_ci REF_CLK_GATE_DIV(gbe0_core, "gbe_core"), 31962306a36Sopenharmony_ci REF_CLK_GATE_DIV(gbe_bm, "gbe_core"), 32062306a36Sopenharmony_ci REF_CLK_FULL_DD(sdio), 32162306a36Sopenharmony_ci REF_CLK_FULL_DD(usb32_usb2_sys), 32262306a36Sopenharmony_ci REF_CLK_FULL_DD(usb32_ss_sys), 32362306a36Sopenharmony_ci REF_CLK_GATE(pcie, "gbe_core"), 32462306a36Sopenharmony_ci { }, 32562306a36Sopenharmony_ci}; 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_cistatic unsigned int get_div(void __iomem *reg, int shift) 32862306a36Sopenharmony_ci{ 32962306a36Sopenharmony_ci u32 val; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci val = (readl(reg) >> shift) & 0x7; 33262306a36Sopenharmony_ci if (val > 6) 33362306a36Sopenharmony_ci return 0; 33462306a36Sopenharmony_ci return val; 33562306a36Sopenharmony_ci} 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_cistatic unsigned long clk_double_div_recalc_rate(struct clk_hw *hw, 33862306a36Sopenharmony_ci unsigned long parent_rate) 33962306a36Sopenharmony_ci{ 34062306a36Sopenharmony_ci struct clk_double_div *double_div = to_clk_double_div(hw); 34162306a36Sopenharmony_ci unsigned int div; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci div = get_div(double_div->reg1, double_div->shift1); 34462306a36Sopenharmony_ci div *= get_div(double_div->reg2, double_div->shift2); 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci return DIV_ROUND_UP_ULL((u64)parent_rate, div); 34762306a36Sopenharmony_ci} 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_cistatic const struct clk_ops clk_double_div_ops = { 35062306a36Sopenharmony_ci .recalc_rate = clk_double_div_recalc_rate, 35162306a36Sopenharmony_ci}; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_cistatic void armada_3700_pm_dvfs_update_regs(unsigned int load_level, 35462306a36Sopenharmony_ci unsigned int *reg, 35562306a36Sopenharmony_ci unsigned int *offset) 35662306a36Sopenharmony_ci{ 35762306a36Sopenharmony_ci if (load_level <= ARMADA_37XX_DVFS_LOAD_1) 35862306a36Sopenharmony_ci *reg = ARMADA_37XX_NB_L0L1; 35962306a36Sopenharmony_ci else 36062306a36Sopenharmony_ci *reg = ARMADA_37XX_NB_L2L3; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci if (load_level == ARMADA_37XX_DVFS_LOAD_0 || 36362306a36Sopenharmony_ci load_level == ARMADA_37XX_DVFS_LOAD_2) 36462306a36Sopenharmony_ci *offset += ARMADA_37XX_NB_CONFIG_SHIFT; 36562306a36Sopenharmony_ci} 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_cistatic bool armada_3700_pm_dvfs_is_enabled(struct regmap *base) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci unsigned int val, reg = ARMADA_37XX_NB_DYN_MOD; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci if (IS_ERR(base)) 37262306a36Sopenharmony_ci return false; 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci regmap_read(base, reg, &val); 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci return !!(val & BIT(ARMADA_37XX_NB_DFS_EN)); 37762306a36Sopenharmony_ci} 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_cistatic unsigned int armada_3700_pm_dvfs_get_cpu_div(struct regmap *base) 38062306a36Sopenharmony_ci{ 38162306a36Sopenharmony_ci unsigned int reg = ARMADA_37XX_NB_CPU_LOAD; 38262306a36Sopenharmony_ci unsigned int offset = ARMADA_37XX_NB_TBG_DIV_OFF; 38362306a36Sopenharmony_ci unsigned int load_level, div; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci /* 38662306a36Sopenharmony_ci * This function is always called after the function 38762306a36Sopenharmony_ci * armada_3700_pm_dvfs_is_enabled, so no need to check again 38862306a36Sopenharmony_ci * if the base is valid. 38962306a36Sopenharmony_ci */ 39062306a36Sopenharmony_ci regmap_read(base, reg, &load_level); 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci /* 39362306a36Sopenharmony_ci * The register and the offset inside this register accessed to 39462306a36Sopenharmony_ci * read the current divider depend on the load level 39562306a36Sopenharmony_ci */ 39662306a36Sopenharmony_ci load_level &= ARMADA_37XX_NB_CPU_LOAD_MASK; 39762306a36Sopenharmony_ci armada_3700_pm_dvfs_update_regs(load_level, ®, &offset); 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci regmap_read(base, reg, &div); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci return (div >> offset) & ARMADA_37XX_NB_TBG_DIV_MASK; 40262306a36Sopenharmony_ci} 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_cistatic unsigned int armada_3700_pm_dvfs_get_cpu_parent(struct regmap *base) 40562306a36Sopenharmony_ci{ 40662306a36Sopenharmony_ci unsigned int reg = ARMADA_37XX_NB_CPU_LOAD; 40762306a36Sopenharmony_ci unsigned int offset = ARMADA_37XX_NB_TBG_SEL_OFF; 40862306a36Sopenharmony_ci unsigned int load_level, sel; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci /* 41162306a36Sopenharmony_ci * This function is always called after the function 41262306a36Sopenharmony_ci * armada_3700_pm_dvfs_is_enabled, so no need to check again 41362306a36Sopenharmony_ci * if the base is valid 41462306a36Sopenharmony_ci */ 41562306a36Sopenharmony_ci regmap_read(base, reg, &load_level); 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci /* 41862306a36Sopenharmony_ci * The register and the offset inside this register accessed to 41962306a36Sopenharmony_ci * read the current divider depend on the load level 42062306a36Sopenharmony_ci */ 42162306a36Sopenharmony_ci load_level &= ARMADA_37XX_NB_CPU_LOAD_MASK; 42262306a36Sopenharmony_ci armada_3700_pm_dvfs_update_regs(load_level, ®, &offset); 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci regmap_read(base, reg, &sel); 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci return (sel >> offset) & ARMADA_37XX_NB_TBG_SEL_MASK; 42762306a36Sopenharmony_ci} 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_cistatic u8 clk_pm_cpu_get_parent(struct clk_hw *hw) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw); 43262306a36Sopenharmony_ci u32 val; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci if (armada_3700_pm_dvfs_is_enabled(pm_cpu->nb_pm_base)) { 43562306a36Sopenharmony_ci val = armada_3700_pm_dvfs_get_cpu_parent(pm_cpu->nb_pm_base); 43662306a36Sopenharmony_ci } else { 43762306a36Sopenharmony_ci val = readl(pm_cpu->reg_mux) >> pm_cpu->shift_mux; 43862306a36Sopenharmony_ci val &= pm_cpu->mask_mux; 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci return val; 44262306a36Sopenharmony_ci} 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_cistatic unsigned long clk_pm_cpu_recalc_rate(struct clk_hw *hw, 44562306a36Sopenharmony_ci unsigned long parent_rate) 44662306a36Sopenharmony_ci{ 44762306a36Sopenharmony_ci struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw); 44862306a36Sopenharmony_ci unsigned int div; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci if (armada_3700_pm_dvfs_is_enabled(pm_cpu->nb_pm_base)) 45162306a36Sopenharmony_ci div = armada_3700_pm_dvfs_get_cpu_div(pm_cpu->nb_pm_base); 45262306a36Sopenharmony_ci else 45362306a36Sopenharmony_ci div = get_div(pm_cpu->reg_div, pm_cpu->shift_div); 45462306a36Sopenharmony_ci return DIV_ROUND_UP_ULL((u64)parent_rate, div); 45562306a36Sopenharmony_ci} 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_cistatic long clk_pm_cpu_round_rate(struct clk_hw *hw, unsigned long rate, 45862306a36Sopenharmony_ci unsigned long *parent_rate) 45962306a36Sopenharmony_ci{ 46062306a36Sopenharmony_ci struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw); 46162306a36Sopenharmony_ci struct regmap *base = pm_cpu->nb_pm_base; 46262306a36Sopenharmony_ci unsigned int div = *parent_rate / rate; 46362306a36Sopenharmony_ci unsigned int load_level; 46462306a36Sopenharmony_ci /* only available when DVFS is enabled */ 46562306a36Sopenharmony_ci if (!armada_3700_pm_dvfs_is_enabled(base)) 46662306a36Sopenharmony_ci return -EINVAL; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci for (load_level = 0; load_level < LOAD_LEVEL_NR; load_level++) { 46962306a36Sopenharmony_ci unsigned int reg, val, offset = ARMADA_37XX_NB_TBG_DIV_OFF; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci armada_3700_pm_dvfs_update_regs(load_level, ®, &offset); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci regmap_read(base, reg, &val); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci val >>= offset; 47662306a36Sopenharmony_ci val &= ARMADA_37XX_NB_TBG_DIV_MASK; 47762306a36Sopenharmony_ci if (val == div) 47862306a36Sopenharmony_ci /* 47962306a36Sopenharmony_ci * We found a load level matching the target 48062306a36Sopenharmony_ci * divider, switch to this load level and 48162306a36Sopenharmony_ci * return. 48262306a36Sopenharmony_ci */ 48362306a36Sopenharmony_ci return *parent_rate / div; 48462306a36Sopenharmony_ci } 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci /* We didn't find any valid divider */ 48762306a36Sopenharmony_ci return -EINVAL; 48862306a36Sopenharmony_ci} 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci/* 49162306a36Sopenharmony_ci * Workaround when base CPU frequnecy is 1000 or 1200 MHz 49262306a36Sopenharmony_ci * 49362306a36Sopenharmony_ci * Switching the CPU from the L2 or L3 frequencies (250/300 or 200 MHz 49462306a36Sopenharmony_ci * respectively) to L0 frequency (1/1.2 GHz) requires a significant 49562306a36Sopenharmony_ci * amount of time to let VDD stabilize to the appropriate 49662306a36Sopenharmony_ci * voltage. This amount of time is large enough that it cannot be 49762306a36Sopenharmony_ci * covered by the hardware countdown register. Due to this, the CPU 49862306a36Sopenharmony_ci * might start operating at L0 before the voltage is stabilized, 49962306a36Sopenharmony_ci * leading to CPU stalls. 50062306a36Sopenharmony_ci * 50162306a36Sopenharmony_ci * To work around this problem, we prevent switching directly from the 50262306a36Sopenharmony_ci * L2/L3 frequencies to the L0 frequency, and instead switch to the L1 50362306a36Sopenharmony_ci * frequency in-between. The sequence therefore becomes: 50462306a36Sopenharmony_ci * 1. First switch from L2/L3 (200/250/300 MHz) to L1 (500/600 MHz) 50562306a36Sopenharmony_ci * 2. Sleep 20ms for stabling VDD voltage 50662306a36Sopenharmony_ci * 3. Then switch from L1 (500/600 MHz) to L0 (1000/1200 MHz). 50762306a36Sopenharmony_ci */ 50862306a36Sopenharmony_cistatic void clk_pm_cpu_set_rate_wa(struct clk_pm_cpu *pm_cpu, 50962306a36Sopenharmony_ci unsigned int new_level, unsigned long rate, 51062306a36Sopenharmony_ci struct regmap *base) 51162306a36Sopenharmony_ci{ 51262306a36Sopenharmony_ci unsigned int cur_level; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci regmap_read(base, ARMADA_37XX_NB_CPU_LOAD, &cur_level); 51562306a36Sopenharmony_ci cur_level &= ARMADA_37XX_NB_CPU_LOAD_MASK; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci if (cur_level == new_level) 51862306a36Sopenharmony_ci return; 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci /* 52162306a36Sopenharmony_ci * System wants to go to L1 on its own. If we are going from L2/L3, 52262306a36Sopenharmony_ci * remember when 20ms will expire. If from L0, set the value so that 52362306a36Sopenharmony_ci * next switch to L0 won't have to wait. 52462306a36Sopenharmony_ci */ 52562306a36Sopenharmony_ci if (new_level == ARMADA_37XX_DVFS_LOAD_1) { 52662306a36Sopenharmony_ci if (cur_level == ARMADA_37XX_DVFS_LOAD_0) 52762306a36Sopenharmony_ci pm_cpu->l1_expiration = jiffies; 52862306a36Sopenharmony_ci else 52962306a36Sopenharmony_ci pm_cpu->l1_expiration = jiffies + msecs_to_jiffies(20); 53062306a36Sopenharmony_ci return; 53162306a36Sopenharmony_ci } 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci /* 53462306a36Sopenharmony_ci * If we are setting to L2/L3, just invalidate L1 expiration time, 53562306a36Sopenharmony_ci * sleeping is not needed. 53662306a36Sopenharmony_ci */ 53762306a36Sopenharmony_ci if (rate < 1000*1000*1000) 53862306a36Sopenharmony_ci goto invalidate_l1_exp; 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci /* 54162306a36Sopenharmony_ci * We are going to L0 with rate >= 1GHz. Check whether we have been at 54262306a36Sopenharmony_ci * L1 for long enough time. If not, go to L1 for 20ms. 54362306a36Sopenharmony_ci */ 54462306a36Sopenharmony_ci if (pm_cpu->l1_expiration && time_is_before_eq_jiffies(pm_cpu->l1_expiration)) 54562306a36Sopenharmony_ci goto invalidate_l1_exp; 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci regmap_update_bits(base, ARMADA_37XX_NB_CPU_LOAD, 54862306a36Sopenharmony_ci ARMADA_37XX_NB_CPU_LOAD_MASK, 54962306a36Sopenharmony_ci ARMADA_37XX_DVFS_LOAD_1); 55062306a36Sopenharmony_ci msleep(20); 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ciinvalidate_l1_exp: 55362306a36Sopenharmony_ci pm_cpu->l1_expiration = 0; 55462306a36Sopenharmony_ci} 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_cistatic int clk_pm_cpu_set_rate(struct clk_hw *hw, unsigned long rate, 55762306a36Sopenharmony_ci unsigned long parent_rate) 55862306a36Sopenharmony_ci{ 55962306a36Sopenharmony_ci struct clk_pm_cpu *pm_cpu = to_clk_pm_cpu(hw); 56062306a36Sopenharmony_ci struct regmap *base = pm_cpu->nb_pm_base; 56162306a36Sopenharmony_ci unsigned int div = parent_rate / rate; 56262306a36Sopenharmony_ci unsigned int load_level; 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci /* only available when DVFS is enabled */ 56562306a36Sopenharmony_ci if (!armada_3700_pm_dvfs_is_enabled(base)) 56662306a36Sopenharmony_ci return -EINVAL; 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci for (load_level = 0; load_level < LOAD_LEVEL_NR; load_level++) { 56962306a36Sopenharmony_ci unsigned int reg, mask, val, 57062306a36Sopenharmony_ci offset = ARMADA_37XX_NB_TBG_DIV_OFF; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci armada_3700_pm_dvfs_update_regs(load_level, ®, &offset); 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci regmap_read(base, reg, &val); 57562306a36Sopenharmony_ci val >>= offset; 57662306a36Sopenharmony_ci val &= ARMADA_37XX_NB_TBG_DIV_MASK; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci if (val == div) { 57962306a36Sopenharmony_ci /* 58062306a36Sopenharmony_ci * We found a load level matching the target 58162306a36Sopenharmony_ci * divider, switch to this load level and 58262306a36Sopenharmony_ci * return. 58362306a36Sopenharmony_ci */ 58462306a36Sopenharmony_ci reg = ARMADA_37XX_NB_CPU_LOAD; 58562306a36Sopenharmony_ci mask = ARMADA_37XX_NB_CPU_LOAD_MASK; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci /* Apply workaround when base CPU frequency is 1000 or 1200 MHz */ 58862306a36Sopenharmony_ci if (parent_rate >= 1000*1000*1000) 58962306a36Sopenharmony_ci clk_pm_cpu_set_rate_wa(pm_cpu, load_level, rate, base); 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci regmap_update_bits(base, reg, mask, load_level); 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci return rate; 59462306a36Sopenharmony_ci } 59562306a36Sopenharmony_ci } 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci /* We didn't find any valid divider */ 59862306a36Sopenharmony_ci return -EINVAL; 59962306a36Sopenharmony_ci} 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_cistatic const struct clk_ops clk_pm_cpu_ops = { 60262306a36Sopenharmony_ci .get_parent = clk_pm_cpu_get_parent, 60362306a36Sopenharmony_ci .round_rate = clk_pm_cpu_round_rate, 60462306a36Sopenharmony_ci .set_rate = clk_pm_cpu_set_rate, 60562306a36Sopenharmony_ci .recalc_rate = clk_pm_cpu_recalc_rate, 60662306a36Sopenharmony_ci}; 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_cistatic const struct of_device_id armada_3700_periph_clock_of_match[] = { 60962306a36Sopenharmony_ci { .compatible = "marvell,armada-3700-periph-clock-nb", 61062306a36Sopenharmony_ci .data = data_nb, }, 61162306a36Sopenharmony_ci { .compatible = "marvell,armada-3700-periph-clock-sb", 61262306a36Sopenharmony_ci .data = data_sb, }, 61362306a36Sopenharmony_ci { } 61462306a36Sopenharmony_ci}; 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_cistatic int armada_3700_add_composite_clk(const struct clk_periph_data *data, 61762306a36Sopenharmony_ci void __iomem *reg, spinlock_t *lock, 61862306a36Sopenharmony_ci struct device *dev, struct clk_hw **hw) 61962306a36Sopenharmony_ci{ 62062306a36Sopenharmony_ci const struct clk_ops *mux_ops = NULL, *gate_ops = NULL, 62162306a36Sopenharmony_ci *rate_ops = NULL; 62262306a36Sopenharmony_ci struct clk_hw *mux_hw = NULL, *gate_hw = NULL, *rate_hw = NULL; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci if (data->mux_hw) { 62562306a36Sopenharmony_ci struct clk_mux *mux; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci mux_hw = data->mux_hw; 62862306a36Sopenharmony_ci mux = to_clk_mux(mux_hw); 62962306a36Sopenharmony_ci mux->lock = lock; 63062306a36Sopenharmony_ci mux_ops = mux_hw->init->ops; 63162306a36Sopenharmony_ci mux->reg = reg + (u64)mux->reg; 63262306a36Sopenharmony_ci } 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci if (data->gate_hw) { 63562306a36Sopenharmony_ci struct clk_gate *gate; 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci gate_hw = data->gate_hw; 63862306a36Sopenharmony_ci gate = to_clk_gate(gate_hw); 63962306a36Sopenharmony_ci gate->lock = lock; 64062306a36Sopenharmony_ci gate_ops = gate_hw->init->ops; 64162306a36Sopenharmony_ci gate->reg = reg + (u64)gate->reg; 64262306a36Sopenharmony_ci gate->flags = CLK_GATE_SET_TO_DISABLE; 64362306a36Sopenharmony_ci } 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci if (data->rate_hw) { 64662306a36Sopenharmony_ci rate_hw = data->rate_hw; 64762306a36Sopenharmony_ci rate_ops = rate_hw->init->ops; 64862306a36Sopenharmony_ci if (data->is_double_div) { 64962306a36Sopenharmony_ci struct clk_double_div *rate; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci rate = to_clk_double_div(rate_hw); 65262306a36Sopenharmony_ci rate->reg1 = reg + (u64)rate->reg1; 65362306a36Sopenharmony_ci rate->reg2 = reg + (u64)rate->reg2; 65462306a36Sopenharmony_ci } else { 65562306a36Sopenharmony_ci struct clk_divider *rate = to_clk_divider(rate_hw); 65662306a36Sopenharmony_ci const struct clk_div_table *clkt; 65762306a36Sopenharmony_ci int table_size = 0; 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci rate->reg = reg + (u64)rate->reg; 66062306a36Sopenharmony_ci for (clkt = rate->table; clkt->div; clkt++) 66162306a36Sopenharmony_ci table_size++; 66262306a36Sopenharmony_ci rate->width = order_base_2(table_size); 66362306a36Sopenharmony_ci rate->lock = lock; 66462306a36Sopenharmony_ci } 66562306a36Sopenharmony_ci } 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci if (data->muxrate_hw) { 66862306a36Sopenharmony_ci struct clk_pm_cpu *pmcpu_clk; 66962306a36Sopenharmony_ci struct clk_hw *muxrate_hw = data->muxrate_hw; 67062306a36Sopenharmony_ci struct regmap *map; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci pmcpu_clk = to_clk_pm_cpu(muxrate_hw); 67362306a36Sopenharmony_ci pmcpu_clk->reg_mux = reg + (u64)pmcpu_clk->reg_mux; 67462306a36Sopenharmony_ci pmcpu_clk->reg_div = reg + (u64)pmcpu_clk->reg_div; 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci mux_hw = muxrate_hw; 67762306a36Sopenharmony_ci rate_hw = muxrate_hw; 67862306a36Sopenharmony_ci mux_ops = muxrate_hw->init->ops; 67962306a36Sopenharmony_ci rate_ops = muxrate_hw->init->ops; 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci map = syscon_regmap_lookup_by_compatible( 68262306a36Sopenharmony_ci "marvell,armada-3700-nb-pm"); 68362306a36Sopenharmony_ci pmcpu_clk->nb_pm_base = map; 68462306a36Sopenharmony_ci } 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci *hw = clk_hw_register_composite(dev, data->name, data->parent_names, 68762306a36Sopenharmony_ci data->num_parents, mux_hw, 68862306a36Sopenharmony_ci mux_ops, rate_hw, rate_ops, 68962306a36Sopenharmony_ci gate_hw, gate_ops, CLK_IGNORE_UNUSED); 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(*hw); 69262306a36Sopenharmony_ci} 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_cistatic int __maybe_unused armada_3700_periph_clock_suspend(struct device *dev) 69562306a36Sopenharmony_ci{ 69662306a36Sopenharmony_ci struct clk_periph_driver_data *data = dev_get_drvdata(dev); 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci data->tbg_sel = readl(data->reg + TBG_SEL); 69962306a36Sopenharmony_ci data->div_sel0 = readl(data->reg + DIV_SEL0); 70062306a36Sopenharmony_ci data->div_sel1 = readl(data->reg + DIV_SEL1); 70162306a36Sopenharmony_ci data->div_sel2 = readl(data->reg + DIV_SEL2); 70262306a36Sopenharmony_ci data->clk_sel = readl(data->reg + CLK_SEL); 70362306a36Sopenharmony_ci data->clk_dis = readl(data->reg + CLK_DIS); 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci return 0; 70662306a36Sopenharmony_ci} 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_cistatic int __maybe_unused armada_3700_periph_clock_resume(struct device *dev) 70962306a36Sopenharmony_ci{ 71062306a36Sopenharmony_ci struct clk_periph_driver_data *data = dev_get_drvdata(dev); 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci /* Follow the same order than what the Cortex-M3 does (ATF code) */ 71362306a36Sopenharmony_ci writel(data->clk_dis, data->reg + CLK_DIS); 71462306a36Sopenharmony_ci writel(data->div_sel0, data->reg + DIV_SEL0); 71562306a36Sopenharmony_ci writel(data->div_sel1, data->reg + DIV_SEL1); 71662306a36Sopenharmony_ci writel(data->div_sel2, data->reg + DIV_SEL2); 71762306a36Sopenharmony_ci writel(data->tbg_sel, data->reg + TBG_SEL); 71862306a36Sopenharmony_ci writel(data->clk_sel, data->reg + CLK_SEL); 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_ci return 0; 72162306a36Sopenharmony_ci} 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_cistatic const struct dev_pm_ops armada_3700_periph_clock_pm_ops = { 72462306a36Sopenharmony_ci SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(armada_3700_periph_clock_suspend, 72562306a36Sopenharmony_ci armada_3700_periph_clock_resume) 72662306a36Sopenharmony_ci}; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_cistatic int armada_3700_periph_clock_probe(struct platform_device *pdev) 72962306a36Sopenharmony_ci{ 73062306a36Sopenharmony_ci struct clk_periph_driver_data *driver_data; 73162306a36Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 73262306a36Sopenharmony_ci const struct clk_periph_data *data; 73362306a36Sopenharmony_ci struct device *dev = &pdev->dev; 73462306a36Sopenharmony_ci int num_periph = 0, i, ret; 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ci data = of_device_get_match_data(dev); 73762306a36Sopenharmony_ci if (!data) 73862306a36Sopenharmony_ci return -ENODEV; 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_ci while (data[num_periph].name) 74162306a36Sopenharmony_ci num_periph++; 74262306a36Sopenharmony_ci 74362306a36Sopenharmony_ci driver_data = devm_kzalloc(dev, sizeof(*driver_data), GFP_KERNEL); 74462306a36Sopenharmony_ci if (!driver_data) 74562306a36Sopenharmony_ci return -ENOMEM; 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_ci driver_data->hw_data = devm_kzalloc(dev, 74862306a36Sopenharmony_ci struct_size(driver_data->hw_data, 74962306a36Sopenharmony_ci hws, num_periph), 75062306a36Sopenharmony_ci GFP_KERNEL); 75162306a36Sopenharmony_ci if (!driver_data->hw_data) 75262306a36Sopenharmony_ci return -ENOMEM; 75362306a36Sopenharmony_ci driver_data->hw_data->num = num_periph; 75462306a36Sopenharmony_ci 75562306a36Sopenharmony_ci driver_data->reg = devm_platform_ioremap_resource(pdev, 0); 75662306a36Sopenharmony_ci if (IS_ERR(driver_data->reg)) 75762306a36Sopenharmony_ci return PTR_ERR(driver_data->reg); 75862306a36Sopenharmony_ci 75962306a36Sopenharmony_ci spin_lock_init(&driver_data->lock); 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci for (i = 0; i < num_periph; i++) { 76262306a36Sopenharmony_ci struct clk_hw **hw = &driver_data->hw_data->hws[i]; 76362306a36Sopenharmony_ci if (armada_3700_add_composite_clk(&data[i], driver_data->reg, 76462306a36Sopenharmony_ci &driver_data->lock, dev, hw)) 76562306a36Sopenharmony_ci dev_err(dev, "Can't register periph clock %s\n", 76662306a36Sopenharmony_ci data[i].name); 76762306a36Sopenharmony_ci } 76862306a36Sopenharmony_ci 76962306a36Sopenharmony_ci ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, 77062306a36Sopenharmony_ci driver_data->hw_data); 77162306a36Sopenharmony_ci if (ret) { 77262306a36Sopenharmony_ci for (i = 0; i < num_periph; i++) 77362306a36Sopenharmony_ci clk_hw_unregister(driver_data->hw_data->hws[i]); 77462306a36Sopenharmony_ci return ret; 77562306a36Sopenharmony_ci } 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci platform_set_drvdata(pdev, driver_data); 77862306a36Sopenharmony_ci return 0; 77962306a36Sopenharmony_ci} 78062306a36Sopenharmony_ci 78162306a36Sopenharmony_cistatic void armada_3700_periph_clock_remove(struct platform_device *pdev) 78262306a36Sopenharmony_ci{ 78362306a36Sopenharmony_ci struct clk_periph_driver_data *data = platform_get_drvdata(pdev); 78462306a36Sopenharmony_ci struct clk_hw_onecell_data *hw_data = data->hw_data; 78562306a36Sopenharmony_ci int i; 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci of_clk_del_provider(pdev->dev.of_node); 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci for (i = 0; i < hw_data->num; i++) 79062306a36Sopenharmony_ci clk_hw_unregister(hw_data->hws[i]); 79162306a36Sopenharmony_ci} 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_cistatic struct platform_driver armada_3700_periph_clock_driver = { 79462306a36Sopenharmony_ci .probe = armada_3700_periph_clock_probe, 79562306a36Sopenharmony_ci .remove_new = armada_3700_periph_clock_remove, 79662306a36Sopenharmony_ci .driver = { 79762306a36Sopenharmony_ci .name = "marvell-armada-3700-periph-clock", 79862306a36Sopenharmony_ci .of_match_table = armada_3700_periph_clock_of_match, 79962306a36Sopenharmony_ci .pm = &armada_3700_periph_clock_pm_ops, 80062306a36Sopenharmony_ci }, 80162306a36Sopenharmony_ci}; 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_cibuiltin_platform_driver(armada_3700_periph_clock_driver); 804