diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-08-16 07:41:21 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-08-16 07:41:21 +0300 |
commit | db06f826ec12bf0701ea7fc0a3c0aa00b84417c8 (patch) | |
tree | 0f5cd2bb7af57574ae8a20bfc0e916512c5f2255 /drivers/clk/meson | |
parent | 6de4c691eab8f421e34c5250f63bf3f477d30eec (diff) | |
parent | ac7da1b787d9ea43680c487613269742c48d8747 (diff) | |
download | linux-db06f826ec12bf0701ea7fc0a3c0aa00b84417c8.tar.xz |
Merge tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux
Pull clk updates from Stephen Boyd:
"The new and exciting feature this time around is in the clk core.
We've added duty cycle support to the clk API so that clk signal duty
cycle ratios can be adjusted while taking into account things like clk
dividers and clk tree hierarchy. So far only one SoC has implemented
support for this, but I expect there will be more to come in the
future.
Outside of the core, we have the usual pile of clk driver updates and
additions. The Amlogic meson driver got the most lines in the diffstat
this time around because it added support for a whole bunch of
hardware and duty cycle configuration. After that the Rockchip PX30,
Qualcomm SDM845, and Renesas SoC drivers fill in a majority of the
diff. We're left with the collection of non-critical fixes after that.
Overall it looks pretty quiet this time.
Core:
- Clk duty cycle support
- Proper CLK_SET_RATE_GATE support throughout the tree
New Drivers:
- Actions Semi Owl series S700 SoC clk driver
- Qualcomm SDM845 display clock controller
- i.MX6SX ocram_s clk support
- Uniphier NAND, USB3 PHY, and SPI clk support
- Qualcomm RPMh clk driver
- i.MX7D mailbox clk support
- Maxim 9485 Programmable Clock Generator
- expose 32 kHz PLL on PXA SoCs
- imx6sll GPIO clk gate support
- Atmel at91 I2S audio clk support
- SI544/SI514 clk on/off support
- i.MX6UL GPIO clock gates in CCM CCGR
- Renesas Crypto Engine clocks on R-Car H3
- Renesas clk support for the new RZ/N1D SoC
- Allwinner A64 display engine clock support
- support for Rockchip's PX30 SoC
- Amlogic Meson axg PCIe and audio clocks
- Amlogic Meson GEN CLK on gxbb, gxl and axg
Updates:
- remove an unused variable from Exynos4412 ISP driver
- fix a thinko bug in SCMI clk division logic
- add missing of_node_put()s in some i.MX clk drivers
- Tegra SDMMC clk jitter improvements with high speed signaling modes
- SPDX tagging for qcom and cs2000-cp drivers
- stop leaking con ids in __clk_put()
- fix a corner case in fixed factor clk probing where node is in DT
but parent clk is registered much later
- Marvell Armada 3700 clk_pm_cpu_get_parent() had an invalid return
value
- i.MX clk init arrays removed in place of CLK_IS_CRITICAL
- convert to CLK_IS_CRITICAL for i.MX51/53 driver
- fix Tegra BPMP driver oops when xlating a NULL clk
- proper default configuration for vic03 and vde clks on Tegra124
- mark Tegra memory controller clks as critical
- fix array bounds clamp in Tegra's emc determine_rate() op
- Ingenic i2s bit update and allow UDC clk to gate
- fix name of aspeed SDC clk define to have only one 'CLK'
- fix i.MX6QDL video clk parent
- critical clk markings for qcom SDM845
- fix Stratix10 mpu_free_clk and sdmmc_free_clk parents
- mark Rockchip's pclk_rkpwm_pmu as critical clock, due to it
supplying the pwm used to drive the logic supply of the rk3399
core"
* tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux: (85 commits)
clk: rockchip: Add pclk_rkpwm_pmu to PMU critical clocks in rk3399
clk: cs2000-cp: convert to SPDX identifiers
clk: scmi: Fix the rounding of clock rate
clk: qcom: Add display clock controller driver for SDM845
clk: mvebu: armada-37xx-periph: Remove unused var num_parents
clk: samsung: Remove unused mout_user_aclk400_mcuisp_p4x12 variable
clk: actions: Add S700 SoC clock support
dt-bindings: clock: Add S700 support for Actions Semi Soc's
clk: actions: Add missing REGMAP_MMIO dependency
clk: uniphier: add clock frequency support for SPI
clk: uniphier: add more USB3 PHY clocks
clk: uniphier: add NAND 200MHz clock
clk: tegra: make sdmmc2 and sdmmc4 as sdmmc clocks
clk: tegra: Add sdmmc mux divider clock
clk: tegra: Refactor fractional divider calculation
clk: tegra: Fix includes required by fence_udelay()
clk: imx6sll: fix missing of_node_put()
clk: imx6ul: fix missing of_node_put()
clk: imx: add ocram_s clock for i.mx6sx
clk: mvebu: armada-37xx-periph: Fix wrong return value in get_parent
...
Diffstat (limited to 'drivers/clk/meson')
-rw-r--r-- | drivers/clk/meson/Kconfig | 28 | ||||
-rw-r--r-- | drivers/clk/meson/Makefile | 4 | ||||
-rw-r--r-- | drivers/clk/meson/axg-audio.c | 845 | ||||
-rw-r--r-- | drivers/clk/meson/axg-audio.h | 127 | ||||
-rw-r--r-- | drivers/clk/meson/axg.c | 244 | ||||
-rw-r--r-- | drivers/clk/meson/axg.h | 8 | ||||
-rw-r--r-- | drivers/clk/meson/clk-audio-divider.c | 110 | ||||
-rw-r--r-- | drivers/clk/meson/clk-phase.c | 63 | ||||
-rw-r--r-- | drivers/clk/meson/clk-triphase.c | 68 | ||||
-rw-r--r-- | drivers/clk/meson/clkc-audio.h | 28 | ||||
-rw-r--r-- | drivers/clk/meson/clkc.h | 11 | ||||
-rw-r--r-- | drivers/clk/meson/gxbb.c | 118 | ||||
-rw-r--r-- | drivers/clk/meson/gxbb.h | 5 | ||||
-rw-r--r-- | drivers/clk/meson/sclk-div.c | 243 |
14 files changed, 1696 insertions, 206 deletions
diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig index 815659eebea3..efaa70f682b4 100644 --- a/drivers/clk/meson/Kconfig +++ b/drivers/clk/meson/Kconfig @@ -1,13 +1,19 @@ config COMMON_CLK_AMLOGIC bool - depends on OF depends on ARCH_MESON || COMPILE_TEST + select COMMON_CLK_REGMAP_MESON + +config COMMON_CLK_AMLOGIC_AUDIO + bool + depends on ARCH_MESON || COMPILE_TEST + select COMMON_CLK_AMLOGIC config COMMON_CLK_MESON_AO bool depends on OF depends on ARCH_MESON || COMPILE_TEST select COMMON_CLK_REGMAP_MESON + select RESET_CONTROLLER config COMMON_CLK_REGMAP_MESON bool @@ -15,9 +21,8 @@ config COMMON_CLK_REGMAP_MESON config COMMON_CLK_MESON8B bool - depends on COMMON_CLK_AMLOGIC + select COMMON_CLK_AMLOGIC select RESET_CONTROLLER - select COMMON_CLK_REGMAP_MESON help Support for the clock controller on AmLogic S802 (Meson8), S805 (Meson8b) and S812 (Meson8m2) devices. Say Y if you @@ -25,10 +30,8 @@ config COMMON_CLK_MESON8B config COMMON_CLK_GXBB bool - depends on COMMON_CLK_AMLOGIC - select RESET_CONTROLLER + select COMMON_CLK_AMLOGIC select COMMON_CLK_MESON_AO - select COMMON_CLK_REGMAP_MESON select MFD_SYSCON help Support for the clock controller on AmLogic S905 devices, aka gxbb. @@ -36,11 +39,18 @@ config COMMON_CLK_GXBB config COMMON_CLK_AXG bool - depends on COMMON_CLK_AMLOGIC - select RESET_CONTROLLER + select COMMON_CLK_AMLOGIC select COMMON_CLK_MESON_AO - select COMMON_CLK_REGMAP_MESON select MFD_SYSCON help Support for the clock controller on AmLogic A113D devices, aka axg. Say Y if you want peripherals and CPU frequency scaling to work. + +config COMMON_CLK_AXG_AUDIO + tristate "Meson AXG Audio Clock Controller Driver" + depends on COMMON_CLK_AXG + select COMMON_CLK_AMLOGIC_AUDIO + select MFD_SYSCON + help + Support for the audio clock controller on AmLogic A113D devices, + aka axg, Say Y if you want audio subsystem to work. diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile index d0d13aeb369a..72ec8c40d848 100644 --- a/drivers/clk/meson/Makefile +++ b/drivers/clk/meson/Makefile @@ -2,9 +2,11 @@ # Makefile for Meson specific clk # -obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-mpll.o clk-audio-divider.o +obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-mpll.o clk-phase.o +obj-$(CONFIG_COMMON_CLK_AMLOGIC_AUDIO) += clk-triphase.o sclk-div.o obj-$(CONFIG_COMMON_CLK_MESON_AO) += meson-aoclk.o obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o gxbb-aoclk-32k.o obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o +obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o obj-$(CONFIG_COMMON_CLK_REGMAP_MESON) += clk-regmap.o diff --git a/drivers/clk/meson/axg-audio.c b/drivers/clk/meson/axg-audio.c new file mode 100644 index 000000000000..a0ed41e73bde --- /dev/null +++ b/drivers/clk/meson/axg-audio.c @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Copyright (c) 2018 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@baylibre.com> + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/init.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#include "clkc-audio.h" +#include "axg-audio.h" + +#define AXG_MST_IN_COUNT 8 +#define AXG_SLV_SCLK_COUNT 10 +#define AXG_SLV_LRCLK_COUNT 10 + +#define AXG_AUD_GATE(_name, _reg, _bit, _pname, _iflags) \ +struct clk_regmap axg_##_name = { \ + .data = &(struct clk_regmap_gate_data){ \ + .offset = (_reg), \ + .bit_idx = (_bit), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = "axg_"#_name, \ + .ops = &clk_regmap_gate_ops, \ + .parent_names = (const char *[]){ _pname }, \ + .num_parents = 1, \ + .flags = CLK_DUTY_CYCLE_PARENT | (_iflags), \ + }, \ +} + +#define AXG_AUD_MUX(_name, _reg, _mask, _shift, _dflags, _pnames, _iflags) \ +struct clk_regmap axg_##_name = { \ + .data = &(struct clk_regmap_mux_data){ \ + .offset = (_reg), \ + .mask = (_mask), \ + .shift = (_shift), \ + .flags = (_dflags), \ + }, \ + .hw.init = &(struct clk_init_data){ \ + .name = "axg_"#_name, \ + .ops = &clk_regmap_mux_ops, \ + .parent_names = (_pnames), \ + .num_parents = ARRAY_SIZE(_pnames), \ + .flags = CLK_DUTY_CYCLE_PARENT | (_iflags), \ + }, \ +} + +#define AXG_AUD_DIV(_name, _reg, _shift, _width, _dflags, _pname, _iflags) \ +struct clk_regmap axg_##_name = { \ + .data = &(struct clk_regmap_div_data){ \ + .offset = (_reg), \ + .shift = (_shift), \ + .width = (_width), \ + .flags = (_dflags), \ + }, \ + .hw.init = &(struct clk_init_data){ \ + .name = "axg_"#_name, \ + .ops = &clk_regmap_divider_ops, \ + .parent_names = (const char *[]) { _pname }, \ + .num_parents = 1, \ + .flags = (_iflags), \ + }, \ +} + +#define AXG_PCLK_GATE(_name, _bit) \ + AXG_AUD_GATE(_name, AUDIO_CLK_GATE_EN, _bit, "axg_audio_pclk", 0) + +/* Audio peripheral clocks */ +static AXG_PCLK_GATE(ddr_arb, 0); +static AXG_PCLK_GATE(pdm, 1); +static AXG_PCLK_GATE(tdmin_a, 2); +static AXG_PCLK_GATE(tdmin_b, 3); +static AXG_PCLK_GATE(tdmin_c, 4); +static AXG_PCLK_GATE(tdmin_lb, 5); +static AXG_PCLK_GATE(tdmout_a, 6); +static AXG_PCLK_GATE(tdmout_b, 7); +static AXG_PCLK_GATE(tdmout_c, 8); +static AXG_PCLK_GATE(frddr_a, 9); +static AXG_PCLK_GATE(frddr_b, 10); +static AXG_PCLK_GATE(frddr_c, 11); +static AXG_PCLK_GATE(toddr_a, 12); +static AXG_PCLK_GATE(toddr_b, 13); +static AXG_PCLK_GATE(toddr_c, 14); +static AXG_PCLK_GATE(loopback, 15); +static AXG_PCLK_GATE(spdifin, 16); +static AXG_PCLK_GATE(spdifout, 17); +static AXG_PCLK_GATE(resample, 18); +static AXG_PCLK_GATE(power_detect, 19); + +/* Audio Master Clocks */ +static const char * const mst_mux_parent_names[] = { + "axg_mst_in0", "axg_mst_in1", "axg_mst_in2", "axg_mst_in3", + "axg_mst_in4", "axg_mst_in5", "axg_mst_in6", "axg_mst_in7", +}; + +#define AXG_MST_MCLK_MUX(_name, _reg) \ + AXG_AUD_MUX(_name##_sel, _reg, 0x7, 24, CLK_MUX_ROUND_CLOSEST, \ + mst_mux_parent_names, CLK_SET_RATE_PARENT) + +static AXG_MST_MCLK_MUX(mst_a_mclk, AUDIO_MCLK_A_CTRL); +static AXG_MST_MCLK_MUX(mst_b_mclk, AUDIO_MCLK_B_CTRL); +static AXG_MST_MCLK_MUX(mst_c_mclk, AUDIO_MCLK_C_CTRL); +static AXG_MST_MCLK_MUX(mst_d_mclk, AUDIO_MCLK_D_CTRL); +static AXG_MST_MCLK_MUX(mst_e_mclk, AUDIO_MCLK_E_CTRL); +static AXG_MST_MCLK_MUX(mst_f_mclk, AUDIO_MCLK_F_CTRL); +static AXG_MST_MCLK_MUX(spdifout_clk, AUDIO_CLK_SPDIFOUT_CTRL); +static AXG_MST_MCLK_MUX(spdifin_clk, AUDIO_CLK_SPDIFIN_CTRL); +static AXG_MST_MCLK_MUX(pdm_dclk, AUDIO_CLK_PDMIN_CTRL0); +static AXG_MST_MCLK_MUX(pdm_sysclk, AUDIO_CLK_PDMIN_CTRL1); + +#define AXG_MST_MCLK_DIV(_name, _reg) \ + AXG_AUD_DIV(_name##_div, _reg, 0, 16, CLK_DIVIDER_ROUND_CLOSEST, \ + "axg_"#_name"_sel", CLK_SET_RATE_PARENT) \ + +static AXG_MST_MCLK_DIV(mst_a_mclk, AUDIO_MCLK_A_CTRL); +static AXG_MST_MCLK_DIV(mst_b_mclk, AUDIO_MCLK_B_CTRL); +static AXG_MST_MCLK_DIV(mst_c_mclk, AUDIO_MCLK_C_CTRL); +static AXG_MST_MCLK_DIV(mst_d_mclk, AUDIO_MCLK_D_CTRL); +static AXG_MST_MCLK_DIV(mst_e_mclk, AUDIO_MCLK_E_CTRL); +static AXG_MST_MCLK_DIV(mst_f_mclk, AUDIO_MCLK_F_CTRL); +static AXG_MST_MCLK_DIV(spdifout_clk, AUDIO_CLK_SPDIFOUT_CTRL); +static AXG_MST_MCLK_DIV(spdifin_clk, AUDIO_CLK_SPDIFIN_CTRL); +static AXG_MST_MCLK_DIV(pdm_dclk, AUDIO_CLK_PDMIN_CTRL0); +static AXG_MST_MCLK_DIV(pdm_sysclk, AUDIO_CLK_PDMIN_CTRL1); + +#define AXG_MST_MCLK_GATE(_name, _reg) \ + AXG_AUD_GATE(_name, _reg, 31, "axg_"#_name"_div", \ + CLK_SET_RATE_PARENT) + +static AXG_MST_MCLK_GATE(mst_a_mclk, AUDIO_MCLK_A_CTRL); +static AXG_MST_MCLK_GATE(mst_b_mclk, AUDIO_MCLK_B_CTRL); +static AXG_MST_MCLK_GATE(mst_c_mclk, AUDIO_MCLK_C_CTRL); +static AXG_MST_MCLK_GATE(mst_d_mclk, AUDIO_MCLK_D_CTRL); +static AXG_MST_MCLK_GATE(mst_e_mclk, AUDIO_MCLK_E_CTRL); +static AXG_MST_MCLK_GATE(mst_f_mclk, AUDIO_MCLK_F_CTRL); +static AXG_MST_MCLK_GATE(spdifout_clk, AUDIO_CLK_SPDIFOUT_CTRL); +static AXG_MST_MCLK_GATE(spdifin_clk, AUDIO_CLK_SPDIFIN_CTRL); +static AXG_MST_MCLK_GATE(pdm_dclk, AUDIO_CLK_PDMIN_CTRL0); +static AXG_MST_MCLK_GATE(pdm_sysclk, AUDIO_CLK_PDMIN_CTRL1); + +/* Sample Clocks */ +#define AXG_MST_SCLK_PRE_EN(_name, _reg) \ + AXG_AUD_GATE(mst_##_name##_sclk_pre_en, _reg, 31, \ + "axg_mst_"#_name"_mclk", 0) + +static AXG_MST_SCLK_PRE_EN(a, AUDIO_MST_A_SCLK_CTRL0); +static AXG_MST_SCLK_PRE_EN(b, AUDIO_MST_B_SCLK_CTRL0); +static AXG_MST_SCLK_PRE_EN(c, AUDIO_MST_C_SCLK_CTRL0); +static AXG_MST_SCLK_PRE_EN(d, AUDIO_MST_D_SCLK_CTRL0); +static AXG_MST_SCLK_PRE_EN(e, AUDIO_MST_E_SCLK_CTRL0); +static AXG_MST_SCLK_PRE_EN(f, AUDIO_MST_F_SCLK_CTRL0); + +#define AXG_AUD_SCLK_DIV(_name, _reg, _div_shift, _div_width, \ + _hi_shift, _hi_width, _pname, _iflags) \ +struct clk_regmap axg_##_name = { \ + .data = &(struct meson_sclk_div_data) { \ + .div = { \ + .reg_off = (_reg), \ + .shift = (_div_shift), \ + .width = (_div_width), \ + }, \ + .hi = { \ + .reg_off = (_reg), \ + .shift = (_hi_shift), \ + .width = (_hi_width), \ + }, \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = "axg_"#_name, \ + .ops = &meson_sclk_div_ops, \ + .parent_names = (const char *[]) { _pname }, \ + .num_parents = 1, \ + .flags = (_iflags), \ + }, \ +} + +#define AXG_MST_SCLK_DIV(_name, _reg) \ + AXG_AUD_SCLK_DIV(mst_##_name##_sclk_div, _reg, 20, 10, 0, 0, \ + "axg_mst_"#_name"_sclk_pre_en", \ + CLK_SET_RATE_PARENT) + +static AXG_MST_SCLK_DIV(a, AUDIO_MST_A_SCLK_CTRL0); +static AXG_MST_SCLK_DIV(b, AUDIO_MST_B_SCLK_CTRL0); +static AXG_MST_SCLK_DIV(c, AUDIO_MST_C_SCLK_CTRL0); +static AXG_MST_SCLK_DIV(d, AUDIO_MST_D_SCLK_CTRL0); +static AXG_MST_SCLK_DIV(e, AUDIO_MST_E_SCLK_CTRL0); +static AXG_MST_SCLK_DIV(f, AUDIO_MST_F_SCLK_CTRL0); + +#define AXG_MST_SCLK_POST_EN(_name, _reg) \ + AXG_AUD_GATE(mst_##_name##_sclk_post_en, _reg, 30, \ + "axg_mst_"#_name"_sclk_div", CLK_SET_RATE_PARENT) + +static AXG_MST_SCLK_POST_EN(a, AUDIO_MST_A_SCLK_CTRL0); +static AXG_MST_SCLK_POST_EN(b, AUDIO_MST_B_SCLK_CTRL0); +static AXG_MST_SCLK_POST_EN(c, AUDIO_MST_C_SCLK_CTRL0); +static AXG_MST_SCLK_POST_EN(d, AUDIO_MST_D_SCLK_CTRL0); +static AXG_MST_SCLK_POST_EN(e, AUDIO_MST_E_SCLK_CTRL0); +static AXG_MST_SCLK_POST_EN(f, AUDIO_MST_F_SCLK_CTRL0); + +#define AXG_AUD_TRIPHASE(_name, _reg, _width, _shift0, _shift1, _shift2, \ + _pname, _iflags) \ +struct clk_regmap axg_##_name = { \ + .data = &(struct meson_clk_triphase_data) { \ + .ph0 = { \ + .reg_off = (_reg), \ + .shift = (_shift0), \ + .width = (_width), \ + }, \ + .ph1 = { \ + .reg_off = (_reg), \ + .shift = (_shift1), \ + .width = (_width), \ + }, \ + .ph2 = { \ + .reg_off = (_reg), \ + .shift = (_shift2), \ + .width = (_width), \ + }, \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = "axg_"#_name, \ + .ops = &meson_clk_triphase_ops, \ + .parent_names = (const char *[]) { _pname }, \ + .num_parents = 1, \ + .flags = CLK_DUTY_CYCLE_PARENT | (_iflags), \ + }, \ +} + +#define AXG_MST_SCLK(_name, _reg) \ + AXG_AUD_TRIPHASE(mst_##_name##_sclk, _reg, 1, 0, 2, 4, \ + "axg_mst_"#_name"_sclk_post_en", CLK_SET_RATE_PARENT) + +static AXG_MST_SCLK(a, AUDIO_MST_A_SCLK_CTRL1); +static AXG_MST_SCLK(b, AUDIO_MST_B_SCLK_CTRL1); +static AXG_MST_SCLK(c, AUDIO_MST_C_SCLK_CTRL1); +static AXG_MST_SCLK(d, AUDIO_MST_D_SCLK_CTRL1); +static AXG_MST_SCLK(e, AUDIO_MST_E_SCLK_CTRL1); +static AXG_MST_SCLK(f, AUDIO_MST_F_SCLK_CTRL1); + +#define AXG_MST_LRCLK_DIV(_name, _reg) \ + AXG_AUD_SCLK_DIV(mst_##_name##_lrclk_div, _reg, 0, 10, 10, 10, \ + "axg_mst_"#_name"_sclk_post_en", 0) \ + +static AXG_MST_LRCLK_DIV(a, AUDIO_MST_A_SCLK_CTRL0); +static AXG_MST_LRCLK_DIV(b, AUDIO_MST_B_SCLK_CTRL0); +static AXG_MST_LRCLK_DIV(c, AUDIO_MST_C_SCLK_CTRL0); +static AXG_MST_LRCLK_DIV(d, AUDIO_MST_D_SCLK_CTRL0); +static AXG_MST_LRCLK_DIV(e, AUDIO_MST_E_SCLK_CTRL0); +static AXG_MST_LRCLK_DIV(f, AUDIO_MST_F_SCLK_CTRL0); + +#define AXG_MST_LRCLK(_name, _reg) \ + AXG_AUD_TRIPHASE(mst_##_name##_lrclk, _reg, 1, 1, 3, 5, \ + "axg_mst_"#_name"_lrclk_div", CLK_SET_RATE_PARENT) + +static AXG_MST_LRCLK(a, AUDIO_MST_A_SCLK_CTRL1); +static AXG_MST_LRCLK(b, AUDIO_MST_B_SCLK_CTRL1); +static AXG_MST_LRCLK(c, AUDIO_MST_C_SCLK_CTRL1); +static AXG_MST_LRCLK(d, AUDIO_MST_D_SCLK_CTRL1); +static AXG_MST_LRCLK(e, AUDIO_MST_E_SCLK_CTRL1); +static AXG_MST_LRCLK(f, AUDIO_MST_F_SCLK_CTRL1); + +static const char * const tdm_sclk_parent_names[] = { + "axg_mst_a_sclk", "axg_mst_b_sclk", "axg_mst_c_sclk", + "axg_mst_d_sclk", "axg_mst_e_sclk", "axg_mst_f_sclk", + "axg_slv_sclk0", "axg_slv_sclk1", "axg_slv_sclk2", + "axg_slv_sclk3", "axg_slv_sclk4", "axg_slv_sclk5", + "axg_slv_sclk6", "axg_slv_sclk7", "axg_slv_sclk8", + "axg_slv_sclk9" +}; + +#define AXG_TDM_SCLK_MUX(_name, _reg) \ + AXG_AUD_MUX(tdm##_name##_sclk_sel, _reg, 0xf, 24, \ + CLK_MUX_ROUND_CLOSEST, \ + tdm_sclk_parent_names, 0) + +static AXG_TDM_SCLK_MUX(in_a, AUDIO_CLK_TDMIN_A_CTRL); +static AXG_TDM_SCLK_MUX(in_b, AUDIO_CLK_TDMIN_B_CTRL); +static AXG_TDM_SCLK_MUX(in_c, AUDIO_CLK_TDMIN_C_CTRL); +static AXG_TDM_SCLK_MUX(in_lb, AUDIO_CLK_TDMIN_LB_CTRL); +static AXG_TDM_SCLK_MUX(out_a, AUDIO_CLK_TDMOUT_A_CTRL); +static AXG_TDM_SCLK_MUX(out_b, AUDIO_CLK_TDMOUT_B_CTRL); +static AXG_TDM_SCLK_MUX(out_c, AUDIO_CLK_TDMOUT_C_CTRL); + +#define AXG_TDM_SCLK_PRE_EN(_name, _reg) \ + AXG_AUD_GATE(tdm##_name##_sclk_pre_en, _reg, 31, \ + "axg_tdm"#_name"_sclk_sel", CLK_SET_RATE_PARENT) + +static AXG_TDM_SCLK_PRE_EN(in_a, AUDIO_CLK_TDMIN_A_CTRL); +static AXG_TDM_SCLK_PRE_EN(in_b, AUDIO_CLK_TDMIN_B_CTRL); +static AXG_TDM_SCLK_PRE_EN(in_c, AUDIO_CLK_TDMIN_C_CTRL); +static AXG_TDM_SCLK_PRE_EN(in_lb, AUDIO_CLK_TDMIN_LB_CTRL); +static AXG_TDM_SCLK_PRE_EN(out_a, AUDIO_CLK_TDMOUT_A_CTRL); +static AXG_TDM_SCLK_PRE_EN(out_b, AUDIO_CLK_TDMOUT_B_CTRL); +static AXG_TDM_SCLK_PRE_EN(out_c, AUDIO_CLK_TDMOUT_C_CTRL); + +#define AXG_TDM_SCLK_POST_EN(_name, _reg) \ + AXG_AUD_GATE(tdm##_name##_sclk_post_en, _reg, 30, \ + "axg_tdm"#_name"_sclk_pre_en", CLK_SET_RATE_PARENT) + +static AXG_TDM_SCLK_POST_EN(in_a, AUDIO_CLK_TDMIN_A_CTRL); +static AXG_TDM_SCLK_POST_EN(in_b, AUDIO_CLK_TDMIN_B_CTRL); +static AXG_TDM_SCLK_POST_EN(in_c, AUDIO_CLK_TDMIN_C_CTRL); +static AXG_TDM_SCLK_POST_EN(in_lb, AUDIO_CLK_TDMIN_LB_CTRL); +static AXG_TDM_SCLK_POST_EN(out_a, AUDIO_CLK_TDMOUT_A_CTRL); +static AXG_TDM_SCLK_POST_EN(out_b, AUDIO_CLK_TDMOUT_B_CTRL); +static AXG_TDM_SCLK_POST_EN(out_c, AUDIO_CLK_TDMOUT_C_CTRL); + +#define AXG_TDM_SCLK(_name, _reg) \ + struct clk_regmap axg_tdm##_name##_sclk = { \ + .data = &(struct meson_clk_phase_data) { \ + .ph = { \ + .reg_off = (_reg), \ + .shift = 29, \ + .width = 1, \ + }, \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = "axg_tdm"#_name"_sclk", \ + .ops = &meson_clk_phase_ops, \ + .parent_names = (const char *[]) \ + { "axg_tdm"#_name"_sclk_post_en" }, \ + .num_parents = 1, \ + .flags = CLK_DUTY_CYCLE_PARENT | CLK_SET_RATE_PARENT, \ + }, \ +} + +static AXG_TDM_SCLK(in_a, AUDIO_CLK_TDMIN_A_CTRL); +static AXG_TDM_SCLK(in_b, AUDIO_CLK_TDMIN_B_CTRL); +static AXG_TDM_SCLK(in_c, AUDIO_CLK_TDMIN_C_CTRL); +static AXG_TDM_SCLK(in_lb, AUDIO_CLK_TDMIN_LB_CTRL); +static AXG_TDM_SCLK(out_a, AUDIO_CLK_TDMOUT_A_CTRL); +static AXG_TDM_SCLK(out_b, AUDIO_CLK_TDMOUT_B_CTRL); +static AXG_TDM_SCLK(out_c, AUDIO_CLK_TDMOUT_C_CTRL); + +static const char * const tdm_lrclk_parent_names[] = { + "axg_mst_a_lrclk", "axg_mst_b_lrclk", "axg_mst_c_lrclk", + "axg_mst_d_lrclk", "axg_mst_e_lrclk", "axg_mst_f_lrclk", + "axg_slv_lrclk0", "axg_slv_lrclk1", "axg_slv_lrclk2", + "axg_slv_lrclk3", "axg_slv_lrclk4", "axg_slv_lrclk5", + "axg_slv_lrclk6", "axg_slv_lrclk7", "axg_slv_lrclk8", + "axg_slv_lrclk9" +}; + +#define AXG_TDM_LRLCK(_name, _reg) \ + AXG_AUD_MUX(tdm##_name##_lrclk, _reg, 0xf, 20, \ + CLK_MUX_ROUND_CLOSEST, \ + tdm_lrclk_parent_names, 0) + +static AXG_TDM_LRLCK(in_a, AUDIO_CLK_TDMIN_A_CTRL); +static AXG_TDM_LRLCK(in_b, AUDIO_CLK_TDMIN_B_CTRL); +static AXG_TDM_LRLCK(in_c, AUDIO_CLK_TDMIN_C_CTRL); +static AXG_TDM_LRLCK(in_lb, AUDIO_CLK_TDMIN_LB_CTRL); +static AXG_TDM_LRLCK(out_a, AUDIO_CLK_TDMOUT_A_CTRL); +static AXG_TDM_LRLCK(out_b, AUDIO_CLK_TDMOUT_B_CTRL); +static AXG_TDM_LRLCK(out_c, AUDIO_CLK_TDMOUT_C_CTRL); + +/* + * Array of all clocks provided by this provider + * The input clocks of the controller will be populated at runtime + */ +static struct clk_hw_onecell_data axg_audio_hw_onecell_data = { + .hws = { + [AUD_CLKID_DDR_ARB] = &axg_ddr_arb.hw, + [AUD_CLKID_PDM] = &axg_pdm.hw, + [AUD_CLKID_TDMIN_A] = &axg_tdmin_a.hw, + [AUD_CLKID_TDMIN_B] = &axg_tdmin_b.hw, + [AUD_CLKID_TDMIN_C] = &axg_tdmin_c.hw, + [AUD_CLKID_TDMIN_LB] = &axg_tdmin_lb.hw, + [AUD_CLKID_TDMOUT_A] = &axg_tdmout_a.hw, + [AUD_CLKID_TDMOUT_B] = &axg_tdmout_b.hw, + [AUD_CLKID_TDMOUT_C] = &axg_tdmout_c.hw, + [AUD_CLKID_FRDDR_A] = &axg_frddr_a.hw, + [AUD_CLKID_FRDDR_B] = &axg_frddr_b.hw, + [AUD_CLKID_FRDDR_C] = &axg_frddr_c.hw, + [AUD_CLKID_TODDR_A] = &axg_toddr_a.hw, + [AUD_CLKID_TODDR_B] = &axg_toddr_b.hw, + [AUD_CLKID_TODDR_C] = &axg_toddr_c.hw, + [AUD_CLKID_LOOPBACK] = &axg_loopback.hw, + [AUD_CLKID_SPDIFIN] = &axg_spdifin.hw, + [AUD_CLKID_SPDIFOUT] = &axg_spdifout.hw, + [AUD_CLKID_RESAMPLE] = &axg_resample.hw, + [AUD_CLKID_POWER_DETECT] = &axg_power_detect.hw, + [AUD_CLKID_MST_A_MCLK_SEL] = &axg_mst_a_mclk_sel.hw, + [AUD_CLKID_MST_B_MCLK_SEL] = &axg_mst_b_mclk_sel.hw, + [AUD_CLKID_MST_C_MCLK_SEL] = &axg_mst_c_mclk_sel.hw, + [AUD_CLKID_MST_D_MCLK_SEL] = &axg_mst_d_mclk_sel.hw, + [AUD_CLKID_MST_E_MCLK_SEL] = &axg_mst_e_mclk_sel.hw, + [AUD_CLKID_MST_F_MCLK_SEL] = &axg_mst_f_mclk_sel.hw, + [AUD_CLKID_MST_A_MCLK_DIV] = &axg_mst_a_mclk_div.hw, + [AUD_CLKID_MST_B_MCLK_DIV] = &axg_mst_b_mclk_div.hw, + [AUD_CLKID_MST_C_MCLK_DIV] = &axg_mst_c_mclk_div.hw, + [AUD_CLKID_MST_D_MCLK_DIV] = &axg_mst_d_mclk_div.hw, + [AUD_CLKID_MST_E_MCLK_DIV] = &axg_mst_e_mclk_div.hw, + [AUD_CLKID_MST_F_MCLK_DIV] = &axg_mst_f_mclk_div.hw, + [AUD_CLKID_MST_A_MCLK] = &axg_mst_a_mclk.hw, + [AUD_CLKID_MST_B_MCLK] = &axg_mst_b_mclk.hw, + [AUD_CLKID_MST_C_MCLK] = &axg_mst_c_mclk.hw, + [AUD_CLKID_MST_D_MCLK] = &axg_mst_d_mclk.hw, + [AUD_CLKID_MST_E_MCLK] = &axg_mst_e_mclk.hw, + [AUD_CLKID_MST_F_MCLK] = &axg_mst_f_mclk.hw, + [AUD_CLKID_SPDIFOUT_CLK_SEL] = &axg_spdifout_clk_sel.hw, + [AUD_CLKID_SPDIFOUT_CLK_DIV] = &axg_spdifout_clk_div.hw, + [AUD_CLKID_SPDIFOUT_CLK] = &axg_spdifout_clk.hw, + [AUD_CLKID_SPDIFIN_CLK_SEL] = &axg_spdifin_clk_sel.hw, + [AUD_CLKID_SPDIFIN_CLK_DIV] = &axg_spdifin_clk_div.hw, + [AUD_CLKID_SPDIFIN_CLK] = &axg_spdifin_clk.hw, + [AUD_CLKID_PDM_DCLK_SEL] = &axg_pdm_dclk_sel.hw, + [AUD_CLKID_PDM_DCLK_DIV] = &axg_pdm_dclk_div.hw, + [AUD_CLKID_PDM_DCLK] = &axg_pdm_dclk.hw, + [AUD_CLKID_PDM_SYSCLK_SEL] = &axg_pdm_sysclk_sel.hw, + [AUD_CLKID_PDM_SYSCLK_DIV] = &axg_pdm_sysclk_div.hw, + [AUD_CLKID_PDM_SYSCLK] = &axg_pdm_sysclk.hw, + [AUD_CLKID_MST_A_SCLK_PRE_EN] = &axg_mst_a_sclk_pre_en.hw, + [AUD_CLKID_MST_B_SCLK_PRE_EN] = &axg_mst_b_sclk_pre_en.hw, + [AUD_CLKID_MST_C_SCLK_PRE_EN] = &axg_mst_c_sclk_pre_en.hw, + [AUD_CLKID_MST_D_SCLK_PRE_EN] = &axg_mst_d_sclk_pre_en.hw, + [AUD_CLKID_MST_E_SCLK_PRE_EN] = &axg_mst_e_sclk_pre_en.hw, + [AUD_CLKID_MST_F_SCLK_PRE_EN] = &axg_mst_f_sclk_pre_en.hw, + [AUD_CLKID_MST_A_SCLK_DIV] = &axg_mst_a_sclk_div.hw, + [AUD_CLKID_MST_B_SCLK_DIV] = &axg_mst_b_sclk_div.hw, + [AUD_CLKID_MST_C_SCLK_DIV] = &axg_mst_c_sclk_div.hw, + [AUD_CLKID_MST_D_SCLK_DIV] = &axg_mst_d_sclk_div.hw, + [AUD_CLKID_MST_E_SCLK_DIV] = &axg_mst_e_sclk_div.hw, + [AUD_CLKID_MST_F_SCLK_DIV] = &axg_mst_f_sclk_div.hw, + [AUD_CLKID_MST_A_SCLK_POST_EN] = &axg_mst_a_sclk_post_en.hw, + [AUD_CLKID_MST_B_SCLK_POST_EN] = &axg_mst_b_sclk_post_en.hw, + [AUD_CLKID_MST_C_SCLK_POST_EN] = &axg_mst_c_sclk_post_en.hw, + [AUD_CLKID_MST_D_SCLK_POST_EN] = &axg_mst_d_sclk_post_en.hw, + [AUD_CLKID_MST_E_SCLK_POST_EN] = &axg_mst_e_sclk_post_en.hw, + [AUD_CLKID_MST_F_SCLK_POST_EN] = &axg_mst_f_sclk_post_en.hw, + [AUD_CLKID_MST_A_SCLK] = &axg_mst_a_sclk.hw, + [AUD_CLKID_MST_B_SCLK] = &axg_mst_b_sclk.hw, + [AUD_CLKID_MST_C_SCLK] = &axg_mst_c_sclk.hw, + [AUD_CLKID_MST_D_SCLK] = &axg_mst_d_sclk.hw, + [AUD_CLKID_MST_E_SCLK] = &axg_mst_e_sclk.hw, + [AUD_CLKID_MST_F_SCLK] = &axg_mst_f_sclk.hw, + [AUD_CLKID_MST_A_LRCLK_DIV] = &axg_mst_a_lrclk_div.hw, + [AUD_CLKID_MST_B_LRCLK_DIV] = &axg_mst_b_lrclk_div.hw, + [AUD_CLKID_MST_C_LRCLK_DIV] = &axg_mst_c_lrclk_div.hw, + [AUD_CLKID_MST_D_LRCLK_DIV] = &axg_mst_d_lrclk_div.hw, + [AUD_CLKID_MST_E_LRCLK_DIV] = &axg_mst_e_lrclk_div.hw, + [AUD_CLKID_MST_F_LRCLK_DIV] = &axg_mst_f_lrclk_div.hw, + [AUD_CLKID_MST_A_LRCLK] = &axg_mst_a_lrclk.hw, + [AUD_CLKID_MST_B_LRCLK] = &axg_mst_b_lrclk.hw, + [AUD_CLKID_MST_C_LRCLK] = &axg_mst_c_lrclk.hw, + [AUD_CLKID_MST_D_LRCLK] = &axg_mst_d_lrclk.hw, + [AUD_CLKID_MST_E_LRCLK] = &axg_mst_e_lrclk.hw, + [AUD_CLKID_MST_F_LRCLK] = &axg_mst_f_lrclk.hw, + [AUD_CLKID_TDMIN_A_SCLK_SEL] = &axg_tdmin_a_sclk_sel.hw, + [AUD_CLKID_TDMIN_B_SCLK_SEL] = &axg_tdmin_b_sclk_sel.hw, + [AUD_CLKID_TDMIN_C_SCLK_SEL] = &axg_tdmin_c_sclk_sel.hw, + [AUD_CLKID_TDMIN_LB_SCLK_SEL] = &axg_tdmin_lb_sclk_sel.hw, + [AUD_CLKID_TDMOUT_A_SCLK_SEL] = &axg_tdmout_a_sclk_sel.hw, + [AUD_CLKID_TDMOUT_B_SCLK_SEL] = &axg_tdmout_b_sclk_sel.hw, + [AUD_CLKID_TDMOUT_C_SCLK_SEL] = &axg_tdmout_c_sclk_sel.hw, + [AUD_CLKID_TDMIN_A_SCLK_PRE_EN] = &axg_tdmin_a_sclk_pre_en.hw, + [AUD_CLKID_TDMIN_B_SCLK_PRE_EN] = &axg_tdmin_b_sclk_pre_en.hw, + [AUD_CLKID_TDMIN_C_SCLK_PRE_EN] = &axg_tdmin_c_sclk_pre_en.hw, + [AUD_CLKID_TDMIN_LB_SCLK_PRE_EN] = &axg_tdmin_lb_sclk_pre_en.hw, + [AUD_CLKID_TDMOUT_A_SCLK_PRE_EN] = &axg_tdmout_a_sclk_pre_en.hw, + [AUD_CLKID_TDMOUT_B_SCLK_PRE_EN] = &axg_tdmout_b_sclk_pre_en.hw, + [AUD_CLKID_TDMOUT_C_SCLK_PRE_EN] = &axg_tdmout_c_sclk_pre_en.hw, + [AUD_CLKID_TDMIN_A_SCLK_POST_EN] = &axg_tdmin_a_sclk_post_en.hw, + [AUD_CLKID_TDMIN_B_SCLK_POST_EN] = &axg_tdmin_b_sclk_post_en.hw, + [AUD_CLKID_TDMIN_C_SCLK_POST_EN] = &axg_tdmin_c_sclk_post_en.hw, + [AUD_CLKID_TDMIN_LB_SCLK_POST_EN] = &axg_tdmin_lb_sclk_post_en.hw, + [AUD_CLKID_TDMOUT_A_SCLK_POST_EN] = &axg_tdmout_a_sclk_post_en.hw, + [AUD_CLKID_TDMOUT_B_SCLK_POST_EN] = &axg_tdmout_b_sclk_post_en.hw, + [AUD_CLKID_TDMOUT_C_SCLK_POST_EN] = &axg_tdmout_c_sclk_post_en.hw, + [AUD_CLKID_TDMIN_A_SCLK] = &axg_tdmin_a_sclk.hw, + [AUD_CLKID_TDMIN_B_SCLK] = &axg_tdmin_b_sclk.hw, + [AUD_CLKID_TDMIN_C_SCLK] = &axg_tdmin_c_sclk.hw, + [AUD_CLKID_TDMIN_LB_SCLK] = &axg_tdmin_lb_sclk.hw, + [AUD_CLKID_TDMOUT_A_SCLK] = &axg_tdmout_a_sclk.hw, + [AUD_CLKID_TDMOUT_B_SCLK] = &axg_tdmout_b_sclk.hw, + [AUD_CLKID_TDMOUT_C_SCLK] = &axg_tdmout_c_sclk.hw, + [AUD_CLKID_TDMIN_A_LRCLK] = &axg_tdmin_a_lrclk.hw, + [AUD_CLKID_TDMIN_B_LRCLK] = &axg_tdmin_b_lrclk.hw, + [AUD_CLKID_TDMIN_C_LRCLK] = &axg_tdmin_c_lrclk.hw, + [AUD_CLKID_TDMIN_LB_LRCLK] = &axg_tdmin_lb_lrclk.hw, + [AUD_CLKID_TDMOUT_A_LRCLK] = &axg_tdmout_a_lrclk.hw, + [AUD_CLKID_TDMOUT_B_LRCLK] = &axg_tdmout_b_lrclk.hw, + [AUD_CLKID_TDMOUT_C_LRCLK] = &axg_tdmout_c_lrclk.hw, + [NR_CLKS] = NULL, + }, + .num = NR_CLKS, +}; + +/* Convenience table to populate regmap in .probe() */ +static struct clk_regmap *const axg_audio_clk_regmaps[] = { + &axg_ddr_arb, + &axg_pdm, + &axg_tdmin_a, + &axg_tdmin_b, + &axg_tdmin_c, + &axg_tdmin_lb, + &axg_tdmout_a, + &axg_tdmout_b, + &axg_tdmout_c, + &axg_frddr_a, + &axg_frddr_b, + &axg_frddr_c, + &axg_toddr_a, + &axg_toddr_b, + &axg_toddr_c, + &axg_loopback, + &axg_spdifin, + &axg_spdifout, + &axg_resample, + &axg_power_detect, + &axg_mst_a_mclk_sel, + &axg_mst_b_mclk_sel, + &axg_mst_c_mclk_sel, + &axg_mst_d_mclk_sel, + &axg_mst_e_mclk_sel, + &axg_mst_f_mclk_sel, + &axg_mst_a_mclk_div, + &axg_mst_b_mclk_div, + &axg_mst_c_mclk_div, + &axg_mst_d_mclk_div, + &axg_mst_e_mclk_div, + &axg_mst_f_mclk_div, + &axg_mst_a_mclk, + &axg_mst_b_mclk, + &axg_mst_c_mclk, + &axg_mst_d_mclk, + &axg_mst_e_mclk, + &axg_mst_f_mclk, + &axg_spdifout_clk_sel, + &axg_spdifout_clk_div, + &axg_spdifout_clk, + &axg_spdifin_clk_sel, + &axg_spdifin_clk_div, + &axg_spdifin_clk, + &axg_pdm_dclk_sel, + &axg_pdm_dclk_div, + &axg_pdm_dclk, + &axg_pdm_sysclk_sel, + &axg_pdm_sysclk_div, + &axg_pdm_sysclk, + &axg_mst_a_sclk_pre_en, + &axg_mst_b_sclk_pre_en, + &axg_mst_c_sclk_pre_en, + &axg_mst_d_sclk_pre_en, + &axg_mst_e_sclk_pre_en, + &axg_mst_f_sclk_pre_en, + &axg_mst_a_sclk_div, + &axg_mst_b_sclk_div, + &axg_mst_c_sclk_div, + &axg_mst_d_sclk_div, + &axg_mst_e_sclk_div, + &axg_mst_f_sclk_div, + &axg_mst_a_sclk_post_en, + &axg_mst_b_sclk_post_en, + &axg_mst_c_sclk_post_en, + &axg_mst_d_sclk_post_en, + &axg_mst_e_sclk_post_en, + &axg_mst_f_sclk_post_en, + &axg_mst_a_sclk, + &axg_mst_b_sclk, + &axg_mst_c_sclk, + &axg_mst_d_sclk, + &axg_mst_e_sclk, + &axg_mst_f_sclk, + &axg_mst_a_lrclk_div, + &axg_mst_b_lrclk_div, + &axg_mst_c_lrclk_div, + &axg_mst_d_lrclk_div, + &axg_mst_e_lrclk_div, + &axg_mst_f_lrclk_div, + &axg_mst_a_lrclk, + &axg_mst_b_lrclk, + &axg_mst_c_lrclk, + &axg_mst_d_lrclk, + &axg_mst_e_lrclk, + &axg_mst_f_lrclk, + &axg_tdmin_a_sclk_sel, + &axg_tdmin_b_sclk_sel, + &axg_tdmin_c_sclk_sel, + &axg_tdmin_lb_sclk_sel, + &axg_tdmout_a_sclk_sel, + &axg_tdmout_b_sclk_sel, + &axg_tdmout_c_sclk_sel, + &axg_tdmin_a_sclk_pre_en, + &axg_tdmin_b_sclk_pre_en, + &axg_tdmin_c_sclk_pre_en, + &axg_tdmin_lb_sclk_pre_en, + &axg_tdmout_a_sclk_pre_en, + &axg_tdmout_b_sclk_pre_en, + &axg_tdmout_c_sclk_pre_en, + &axg_tdmin_a_sclk_post_en, + &axg_tdmin_b_sclk_post_en, + &axg_tdmin_c_sclk_post_en, + &axg_tdmin_lb_sclk_post_en, + &axg_tdmout_a_sclk_post_en, + &axg_tdmout_b_sclk_post_en, + &axg_tdmout_c_sclk_post_en, + &axg_tdmin_a_sclk, + &axg_tdmin_b_sclk, + &axg_tdmin_c_sclk, + &axg_tdmin_lb_sclk, + &axg_tdmout_a_sclk, + &axg_tdmout_b_sclk, + &axg_tdmout_c_sclk, + &axg_tdmin_a_lrclk, + &axg_tdmin_b_lrclk, + &axg_tdmin_c_lrclk, + &axg_tdmin_lb_lrclk, + &axg_tdmout_a_lrclk, + &axg_tdmout_b_lrclk, + &axg_tdmout_c_lrclk, +}; + +static struct clk *devm_clk_get_enable(struct device *dev, char *id) +{ + struct clk *clk; + int ret; + + clk = devm_clk_get(dev, id); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -EPROBE_DEFER) + dev_err(dev, "failed to get %s", id); + return clk; + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(dev, "failed to enable %s", id); + return ERR_PTR(ret); + } + + ret = devm_add_action_or_reset(dev, + (void(*)(void *))clk_disable_unprepare, + clk); + if (ret) { + dev_err(dev, "failed to add reset action on %s", id); + return ERR_PTR(ret); + } + + return clk; +} + +static const struct clk_ops axg_clk_no_ops = {}; + +static struct clk_hw *axg_clk_hw_register_bypass(struct device *dev, + const char *name, + const char *parent_name) +{ + struct clk_hw *hw; + struct clk_init_data init; + char *clk_name; + int ret; + + hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL); + if (!hw) + return ERR_PTR(-ENOMEM); + + clk_name = kasprintf(GFP_KERNEL, "axg_%s", name); + if (!clk_name) + return ERR_PTR(-ENOMEM); + + init.name = clk_name; + init.ops = &axg_clk_no_ops; + init.flags = 0; + init.parent_names = parent_name ? &parent_name : NULL; + init.num_parents = parent_name ? 1 : 0; + hw->init = &init; + + ret = devm_clk_hw_register(dev, hw); + kfree(clk_name); + + return ret ? ERR_PTR(ret) : hw; +} + +static int axg_register_clk_hw_input(struct device *dev, + const char *name, + unsigned int clkid) +{ + struct clk *parent_clk = devm_clk_get(dev, name); + struct clk_hw *hw = NULL; + + if (IS_ERR(parent_clk)) { + int err = PTR_ERR(parent_clk); + + /* It is ok if an input clock is missing */ + if (err == -ENOENT) { + dev_dbg(dev, "%s not provided", name); + } else { + if (err != -EPROBE_DEFER) + dev_err(dev, "failed to get %s clock", name); + return err; + } + } else { + hw = axg_clk_hw_register_bypass(dev, name, + __clk_get_name(parent_clk)); + } + + if (IS_ERR(hw)) { + dev_err(dev, "failed to register %s clock", name); + return PTR_ERR(hw); + } + + axg_audio_hw_onecell_data.hws[clkid] = hw; + return 0; +} + +static int axg_register_clk_hw_inputs(struct device *dev, + const char *basename, + unsigned int count, + unsigned int clkid) +{ + char *name; + int i, ret; + + for (i = 0; i < count; i++) { + name = kasprintf(GFP_KERNEL, "%s%d", basename, i); + if (!name) + return -ENOMEM; + + ret = axg_register_clk_hw_input(dev, name, clkid + i); + kfree(name); + if (ret) + return ret; + } + + return 0; +} + +static const struct regmap_config axg_audio_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = AUDIO_CLK_PDMIN_CTRL1, +}; + +static int axg_audio_clkc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct regmap *map; + struct resource *res; + void __iomem *regs; + struct clk *clk; + struct clk_hw *hw; + int ret, i; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + map = devm_regmap_init_mmio(dev, regs, &axg_audio_regmap_cfg); + if (IS_ERR(map)) { + dev_err(dev, "failed to init regmap: %ld\n", PTR_ERR(map)); + return PTR_ERR(map); + } + + /* Get the mandatory peripheral clock */ + clk = devm_clk_get_enable(dev, "pclk"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = device_reset(dev); + if (ret) { + dev_err(dev, "failed to reset device\n"); + return ret; + } + + /* Register the peripheral input clock */ + hw = axg_clk_hw_register_bypass(dev, "audio_pclk", + __clk_get_name(clk)); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + axg_audio_hw_onecell_data.hws[AUD_CLKID_PCLK] = hw; + + /* Register optional input master clocks */ + ret = axg_register_clk_hw_inputs(dev, "mst_in", + AXG_MST_IN_COUNT, + AUD_CLKID_MST0); + if (ret) + return ret; + + /* Register optional input slave sclks */ + ret = axg_register_clk_hw_inputs(dev, "slv_sclk", + AXG_SLV_SCLK_COUNT, + AUD_CLKID_SLV_SCLK0); + if (ret) + return ret; + + /* Register optional input slave lrclks */ + ret = axg_register_clk_hw_inputs(dev, "slv_lrclk", + AXG_SLV_LRCLK_COUNT, + AUD_CLKID_SLV_LRCLK0); + if (ret) + return ret; + + /* Populate regmap for the regmap backed clocks */ + for (i = 0; i < ARRAY_SIZE(axg_audio_clk_regmaps); i++) + axg_audio_clk_regmaps[i]->map = map; + + /* Take care to skip the registered input clocks */ + for (i = AUD_CLKID_DDR_ARB; i < axg_audio_hw_onecell_data.num; i++) { + hw = axg_audio_hw_onecell_data.hws[i]; + /* array might be sparse */ + if (!hw) + continue; + + ret = devm_clk_hw_register(dev, hw); + if (ret) { + dev_err(dev, "failed to register clock %s\n", + hw->init->name); + return ret; + } + } + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, + &axg_audio_hw_onecell_data); +} + +static const struct of_device_id clkc_match_table[] = { + { .compatible = "amlogic,axg-audio-clkc" }, + {} +}; +MODULE_DEVICE_TABLE(of, clkc_match_table); + +static struct platform_driver axg_audio_driver = { + .probe = axg_audio_clkc_probe, + .driver = { + .name = "axg-audio-clkc", + .of_match_table = clkc_match_table, + }, +}; +module_platform_driver(axg_audio_driver); + +MODULE_DESCRIPTION("Amlogic A113x Audio Clock driver"); +MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/meson/axg-audio.h b/drivers/clk/meson/axg-audio.h new file mode 100644 index 000000000000..7191b39c9d65 --- /dev/null +++ b/drivers/clk/meson/axg-audio.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +/* + * Copyright (c) 2018 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@baylibre.com> + */ + +#ifndef __AXG_AUDIO_CLKC_H +#define __AXG_AUDIO_CLKC_H + +/* + * Audio Clock register offsets + * + * Register offsets from the datasheet must be multiplied by 4 before + * to get the right offset + */ +#define AUDIO_CLK_GATE_EN 0x000 +#define AUDIO_MCLK_A_CTRL 0x004 +#define AUDIO_MCLK_B_CTRL 0x008 +#define AUDIO_MCLK_C_CTRL 0x00C +#define AUDIO_MCLK_D_CTRL 0x010 +#define AUDIO_MCLK_E_CTRL 0x014 +#define AUDIO_MCLK_F_CTRL 0x018 +#define AUDIO_MST_A_SCLK_CTRL0 0x040 +#define AUDIO_MST_A_SCLK_CTRL1 0x044 +#define AUDIO_MST_B_SCLK_CTRL0 0x048 +#define AUDIO_MST_B_SCLK_CTRL1 0x04C +#define AUDIO_MST_C_SCLK_CTRL0 0x050 +#define AUDIO_MST_C_SCLK_CTRL1 0x054 +#define AUDIO_MST_D_SCLK_CTRL0 0x058 +#define AUDIO_MST_D_SCLK_CTRL1 0x05C +#define AUDIO_MST_E_SCLK_CTRL0 0x060 +#define AUDIO_MST_E_SCLK_CTRL1 0x064 +#define AUDIO_MST_F_SCLK_CTRL0 0x068 +#define AUDIO_MST_F_SCLK_CTRL1 0x06C +#define AUDIO_CLK_TDMIN_A_CTRL 0x080 +#define AUDIO_CLK_TDMIN_B_CTRL 0x084 +#define AUDIO_CLK_TDMIN_C_CTRL 0x088 +#define AUDIO_CLK_TDMIN_LB_CTRL 0x08C +#define AUDIO_CLK_TDMOUT_A_CTRL 0x090 +#define AUDIO_CLK_TDMOUT_B_CTRL 0x094 +#define AUDIO_CLK_TDMOUT_C_CTRL 0x098 +#define AUDIO_CLK_SPDIFIN_CTRL 0x09C +#define AUDIO_CLK_SPDIFOUT_CTRL 0x0A0 +#define AUDIO_CLK_RESAMPLE_CTRL 0x0A4 +#define AUDIO_CLK_LOCKER_CTRL 0x0A8 +#define AUDIO_CLK_PDMIN_CTRL0 0x0AC +#define AUDIO_CLK_PDMIN_CTRL1 0x0B0 + +/* + * CLKID index values + * These indices are entirely contrived and do not map onto the hardware. + */ + +#define AUD_CLKID_PCLK 0 +#define AUD_CLKID_MST0 1 +#define AUD_CLKID_MST1 2 +#define AUD_CLKID_MST2 3 +#define AUD_CLKID_MST3 4 +#define AUD_CLKID_MST4 5 +#define AUD_CLKID_MST5 6 +#define AUD_CLKID_MST6 7 +#define AUD_CLKID_MST7 8 +#define AUD_CLKID_MST_A_MCLK_SEL 59 +#define AUD_CLKID_MST_B_MCLK_SEL 60 +#define AUD_CLKID_MST_C_MCLK_SEL 61 +#define AUD_CLKID_MST_D_MCLK_SEL 62 +#define AUD_CLKID_MST_E_MCLK_SEL 63 +#define AUD_CLKID_MST_F_MCLK_SEL 64 +#define AUD_CLKID_MST_A_MCLK_DIV 65 +#define AUD_CLKID_MST_B_MCLK_DIV 66 +#define AUD_CLKID_MST_C_MCLK_DIV 67 +#define AUD_CLKID_MST_D_MCLK_DIV 68 +#define AUD_CLKID_MST_E_MCLK_DIV 69 +#define AUD_CLKID_MST_F_MCLK_DIV 70 +#define AUD_CLKID_SPDIFOUT_CLK_SEL 71 +#define AUD_CLKID_SPDIFOUT_CLK_DIV 72 +#define AUD_CLKID_SPDIFIN_CLK_SEL 73 +#define AUD_CLKID_SPDIFIN_CLK_DIV 74 +#define AUD_CLKID_PDM_DCLK_SEL 75 +#define AUD_CLKID_PDM_DCLK_DIV 76 +#define AUD_CLKID_PDM_SYSCLK_SEL 77 +#define AUD_CLKID_PDM_SYSCLK_DIV 78 +#define AUD_CLKID_MST_A_SCLK_PRE_EN 92 +#define AUD_CLKID_MST_B_SCLK_PRE_EN 93 +#define AUD_CLKID_MST_C_SCLK_PRE_EN 94 +#define AUD_CLKID_MST_D_SCLK_PRE_EN 95 +#define AUD_CLKID_MST_E_SCLK_PRE_EN 96 +#define AUD_CLKID_MST_F_SCLK_PRE_EN 97 +#define AUD_CLKID_MST_A_SCLK_DIV 98 +#define AUD_CLKID_MST_B_SCLK_DIV 99 +#define AUD_CLKID_MST_C_SCLK_DIV 100 +#define AUD_CLKID_MST_D_SCLK_DIV 101 +#define AUD_CLKID_MST_E_SCLK_DIV 102 +#define AUD_CLKID_MST_F_SCLK_DIV 103 +#define AUD_CLKID_MST_A_SCLK_POST_EN 104 +#define AUD_CLKID_MST_B_SCLK_POST_EN 105 +#define AUD_CLKID_MST_C_SCLK_POST_EN 106 +#define AUD_CLKID_MST_D_SCLK_POST_EN 107 +#define AUD_CLKID_MST_E_SCLK_POST_EN 108 +#define AUD_CLKID_MST_F_SCLK_POST_EN 109 +#define AUD_CLKID_MST_A_LRCLK_DIV 110 +#define AUD_CLKID_MST_B_LRCLK_DIV 111 +#define AUD_CLKID_MST_C_LRCLK_DIV 112 +#define AUD_CLKID_MST_D_LRCLK_DIV 113 +#define AUD_CLKID_MST_E_LRCLK_DIV 114 +#define AUD_CLKID_MST_F_LRCLK_DIV 115 +#define AUD_CLKID_TDMIN_A_SCLK_PRE_EN 137 +#define AUD_CLKID_TDMIN_B_SCLK_PRE_EN 138 +#define AUD_CLKID_TDMIN_C_SCLK_PRE_EN 139 +#define AUD_CLKID_TDMIN_LB_SCLK_PRE_EN 140 +#define AUD_CLKID_TDMOUT_A_SCLK_PRE_EN 141 +#define AUD_CLKID_TDMOUT_B_SCLK_PRE_EN 142 +#define AUD_CLKID_TDMOUT_C_SCLK_PRE_EN 143 +#define AUD_CLKID_TDMIN_A_SCLK_POST_EN 144 +#define AUD_CLKID_TDMIN_B_SCLK_POST_EN 145 +#define AUD_CLKID_TDMIN_C_SCLK_POST_EN 146 +#define AUD_CLKID_TDMIN_LB_SCLK_POST_EN 147 +#define AUD_CLKID_TDMOUT_A_SCLK_POST_EN 148 +#define AUD_CLKID_TDMOUT_B_SCLK_POST_EN 149 +#define AUD_CLKID_TDMOUT_C_SCLK_POST_EN 150 + +/* include the CLKIDs which are part of the DT bindings */ +#include <dt-bindings/clock/axg-audio-clkc.h> + +#define NR_CLKS 151 + +#endif /*__AXG_AUDIO_CLKC_H */ diff --git a/drivers/clk/meson/axg.c b/drivers/clk/meson/axg.c index bd4dbc696b88..00ce62ad6416 100644 --- a/drivers/clk/meson/axg.c +++ b/drivers/clk/meson/axg.c @@ -12,7 +12,6 @@ #include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/init.h> -#include <linux/of_address.h> #include <linux/of_device.h> #include <linux/mfd/syscon.h> #include <linux/platform_device.h> @@ -626,6 +625,137 @@ static struct clk_regmap axg_mpll3 = { }, }; +static const struct pll_rate_table axg_pcie_pll_rate_table[] = { + { + .rate = 100000000, + .m = 200, + .n = 3, + .od = 1, + .od2 = 3, + }, + { /* sentinel */ }, +}; + +static const struct reg_sequence axg_pcie_init_regs[] = { + { .reg = HHI_PCIE_PLL_CNTL, .def = 0x400106c8 }, + { .reg = HHI_PCIE_PLL_CNTL1, .def = 0x0084a2aa }, + { .reg = HHI_PCIE_PLL_CNTL2, .def = 0xb75020be }, + { .reg = HHI_PCIE_PLL_CNTL3, .def = 0x0a47488e }, + { .reg = HHI_PCIE_PLL_CNTL4, .def = 0xc000004d }, + { .reg = HHI_PCIE_PLL_CNTL5, .def = 0x00078000 }, + { .reg = HHI_PCIE_PLL_CNTL6, .def = 0x002323c6 }, +}; + +static struct clk_regmap axg_pcie_pll = { + .data = &(struct meson_clk_pll_data){ + .m = { + .reg_off = HHI_PCIE_PLL_CNTL, + .shift = 0, + .width = 9, + }, + .n = { + .reg_off = HHI_PCIE_PLL_CNTL, + .shift = 9, + .width = 5, + }, + .od = { + .reg_off = HHI_PCIE_PLL_CNTL, + .shift = 16, + .width = 2, + }, + .od2 = { + .reg_off = HHI_PCIE_PLL_CNTL6, + .shift = 6, + .width = 2, + }, + .frac = { + .reg_off = HHI_PCIE_PLL_CNTL1, + .shift = 0, + .width = 12, + }, + .l = { + .reg_off = HHI_PCIE_PLL_CNTL, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = HHI_PCIE_PLL_CNTL, + .shift = 29, + .width = 1, + }, + .table = axg_pcie_pll_rate_table, + .init_regs = axg_pcie_init_regs, + .init_count = ARRAY_SIZE(axg_pcie_init_regs), + }, + .hw.init = &(struct clk_init_data){ + .name = "pcie_pll", + .ops = &meson_clk_pll_ops, + .parent_names = (const char *[]){ "xtal" }, + .num_parents = 1, + }, +}; + +static struct clk_regmap axg_pcie_mux = { + .data = &(struct clk_regmap_mux_data){ + .offset = HHI_PCIE_PLL_CNTL6, + .mask = 0x1, + .shift = 2, + }, + .hw.init = &(struct clk_init_data){ + .name = "pcie_mux", + .ops = &clk_regmap_mux_ops, + .parent_names = (const char *[]){ "mpll3", "pcie_pll" }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap axg_pcie_ref = { + .data = &(struct clk_regmap_mux_data){ + .offset = HHI_PCIE_PLL_CNTL6, + .mask = 0x1, + .shift = 1, + /* skip the parent 0, reserved for debug */ + .table = (u32[]){ 1 }, + }, + .hw.init = &(struct clk_init_data){ + .name = "pcie_ref", + .ops = &clk_regmap_mux_ops, + .parent_names = (const char *[]){ "pcie_mux" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap axg_pcie_cml_en0 = { + .data = &(struct clk_regmap_gate_data){ + .offset = HHI_PCIE_PLL_CNTL6, + .bit_idx = 4, + }, + .hw.init = &(struct clk_init_data) { + .name = "pcie_cml_en0", + .ops = &clk_regmap_gate_ops, + .parent_names = (const char *[]){ "pcie_ref" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + + }, +}; + +static struct clk_regmap axg_pcie_cml_en1 = { + .data = &(struct clk_regmap_gate_data){ + .offset = HHI_PCIE_PLL_CNTL6, + .bit_idx = 3, + }, + .hw.init = &(struct clk_init_data) { + .name = "pcie_cml_en1", + .ops = &clk_regmap_gate_ops, + .parent_names = (const char *[]){ "pcie_ref" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + static u32 mux_table_clk81[] = { 0, 2, 3, 4, 5, 6, 7 }; static const char * const clk81_parent_names[] = { "xtal", "fclk_div7", "mpll1", "mpll2", "fclk_div4", @@ -779,6 +909,63 @@ static struct clk_regmap axg_sd_emmc_c_clk0 = { }, }; +static u32 mux_table_gen_clk[] = { 0, 4, 5, 6, 7, 8, + 9, 10, 11, 13, 14, }; +static const char * const gen_clk_parent_names[] = { + "xtal", "hifi_pll", "mpll0", "mpll1", "mpll2", "mpll3", + "fclk_div4", "fclk_div3", "fclk_div5", "fclk_div7", "gp0_pll", +}; + +static struct clk_regmap axg_gen_clk_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HHI_GEN_CLK_CNTL, + .mask = 0xf, + .shift = 12, + .table = mux_table_gen_clk, + }, + .hw.init = &(struct clk_init_data){ + .name = "gen_clk_sel", + .ops = &clk_regmap_mux_ops, + /* + * bits 15:12 selects from 14 possible parents: + * xtal, [rtc_oscin_i], [sys_cpu_div16], [ddr_dpll_pt], + * hifi_pll, mpll0, mpll1, mpll2, mpll3, fdiv4, + * fdiv3, fdiv5, [cts_msr_clk], fdiv7, gp0_pll + */ + .parent_names = gen_clk_parent_names, + .num_parents = ARRAY_SIZE(gen_clk_parent_names), + }, +}; + +static struct clk_regmap axg_gen_clk_div = { + .data = &(struct clk_regmap_div_data){ + .offset = HHI_GEN_CLK_CNTL, + .shift = 0, + .width = 11, + }, + .hw.init = &(struct clk_init_data){ + .name = "gen_clk_div", + .ops = &clk_regmap_divider_ops, + .parent_names = (const char *[]){ "gen_clk_sel" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap axg_gen_clk = { + .data = &(struct clk_regmap_gate_data){ + .offset = HHI_GEN_CLK_CNTL, + .bit_idx = 7, + }, + .hw.init = &(struct clk_init_data){ + .name = "gen_clk", + .ops = &clk_regmap_gate_ops, + .parent_names = (const char *[]){ "gen_clk_div" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + /* Everything Else (EE) domain gates */ static MESON_GATE(axg_ddr, HHI_GCLK_MPEG0, 0); static MESON_GATE(axg_audio_locker, HHI_GCLK_MPEG0, 2); @@ -821,6 +1008,7 @@ static MESON_GATE(axg_mmc_pclk, HHI_GCLK_MPEG2, 11); static MESON_GATE(axg_vpu_intr, HHI_GCLK_MPEG2, 25); static MESON_GATE(axg_sec_ahb_ahb3_bridge, HHI_GCLK_MPEG2, 26); static MESON_GATE(axg_gic, HHI_GCLK_MPEG2, 30); +static MESON_GATE(axg_mipi_enable, HHI_MIPI_CNTL0, 29); /* Always On (AO) domain gates */ @@ -910,6 +1098,15 @@ static struct clk_hw_onecell_data axg_hw_onecell_data = { [CLKID_FCLK_DIV4_DIV] = &axg_fclk_div4_div.hw, [CLKID_FCLK_DIV5_DIV] = &axg_fclk_div5_div.hw, [CLKID_FCLK_DIV7_DIV] = &axg_fclk_div7_div.hw, + [CLKID_PCIE_PLL] = &axg_pcie_pll.hw, + [CLKID_PCIE_MUX] = &axg_pcie_mux.hw, + [CLKID_PCIE_REF] = &axg_pcie_ref.hw, + [CLKID_PCIE_CML_EN0] = &axg_pcie_cml_en0.hw, + [CLKID_PCIE_CML_EN1] = &axg_pcie_cml_en1.hw, + [CLKID_MIPI_ENABLE] = &axg_mipi_enable.hw, + [CLKID_GEN_CLK_SEL] = &axg_gen_clk_sel.hw, + [CLKID_GEN_CLK_DIV] = &axg_gen_clk_div.hw, + [CLKID_GEN_CLK] = &axg_gen_clk.hw, [NR_CLKS] = NULL, }, .num = NR_CLKS, @@ -988,6 +1185,15 @@ static struct clk_regmap *const axg_clk_regmaps[] = { &axg_fclk_div4, &axg_fclk_div5, &axg_fclk_div7, + &axg_pcie_pll, + &axg_pcie_mux, + &axg_pcie_ref, + &axg_pcie_cml_en0, + &axg_pcie_cml_en1, + &axg_mipi_enable, + &axg_gen_clk_sel, + &axg_gen_clk_div, + &axg_gen_clk, }; static const struct of_device_id clkc_match_table[] = { @@ -995,49 +1201,17 @@ static const struct of_device_id clkc_match_table[] = { {} }; -static const struct regmap_config clkc_regmap_config = { - .reg_bits = 32, - .val_bits = 32, - .reg_stride = 4, -}; - static int axg_clkc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *res; - void __iomem *clk_base = NULL; struct regmap *map; int ret, i; /* Get the hhi system controller node if available */ map = syscon_node_to_regmap(of_get_parent(dev->of_node)); if (IS_ERR(map)) { - dev_err(dev, - "failed to get HHI regmap - Trying obsolete regs\n"); - - /* - * FIXME: HHI registers should be accessed through - * the appropriate system controller. This is required because - * there is more than just clocks in this register space - * - * This fallback method is only provided temporarily until - * all the platform DTs are properly using the syscon node - */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -EINVAL; - - - clk_base = devm_ioremap(dev, res->start, resource_size(res)); - if (!clk_base) { - dev_err(dev, "Unable to map clk base\n"); - return -ENXIO; - } - - map = devm_regmap_init_mmio(dev, clk_base, - &clkc_regmap_config); - if (IS_ERR(map)) - return PTR_ERR(map); + dev_err(dev, "failed to get HHI regmap\n"); + return PTR_ERR(map); } /* Populate regmap for the regmap backed clocks */ diff --git a/drivers/clk/meson/axg.h b/drivers/clk/meson/axg.h index b421df6a7ea0..1d04144a1b2c 100644 --- a/drivers/clk/meson/axg.h +++ b/drivers/clk/meson/axg.h @@ -16,6 +16,7 @@ * Register offsets from the data sheet must be multiplied by 4 before * adding them to the base address to get the right value. */ +#define HHI_MIPI_CNTL0 0x00 #define HHI_GP0_PLL_CNTL 0x40 #define HHI_GP0_PLL_CNTL2 0x44 #define HHI_GP0_PLL_CNTL3 0x48 @@ -127,8 +128,13 @@ #define CLKID_FCLK_DIV4_DIV 73 #define CLKID_FCLK_DIV5_DIV 74 #define CLKID_FCLK_DIV7_DIV 75 +#define CLKID_PCIE_PLL 76 +#define CLKID_PCIE_MUX 77 +#define CLKID_PCIE_REF 78 +#define CLKID_GEN_CLK_SEL 82 +#define CLKID_GEN_CLK_DIV 83 -#define NR_CLKS 76 +#define NR_CLKS 85 /* include the CLKIDs that have been made part of the DT binding */ #include <dt-bindings/clock/axg-clkc.h> diff --git a/drivers/clk/meson/clk-audio-divider.c b/drivers/clk/meson/clk-audio-divider.c deleted file mode 100644 index e4cf96ba704e..000000000000 --- a/drivers/clk/meson/clk-audio-divider.c +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (c) 2017 AmLogic, Inc. - * Author: Jerome Brunet <jbrunet@baylibre.com> - */ - -/* - * i2s master clock divider: The algorithm of the generic clk-divider used with - * a very precise clock parent such as the mpll tends to select a low divider - * factor. This gives poor results with this particular divider, especially with - * high frequencies (> 100 MHz) - * - * This driver try to select the maximum possible divider with the rate the - * upstream clock can provide. - */ - -#include <linux/clk-provider.h> -#include "clkc.h" - -static inline struct meson_clk_audio_div_data * -meson_clk_audio_div_data(struct clk_regmap *clk) -{ - return (struct meson_clk_audio_div_data *)clk->data; -} - -static int _div_round(unsigned long parent_rate, unsigned long rate, - unsigned long flags) -{ - if (flags & CLK_DIVIDER_ROUND_CLOSEST) - return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, rate); - - return DIV_ROUND_UP_ULL((u64)parent_rate, rate); -} - -static int _get_val(unsigned long parent_rate, unsigned long rate) -{ - return DIV_ROUND_UP_ULL((u64)parent_rate, rate) - 1; -} - -static int _valid_divider(unsigned int width, int divider) -{ - int max_divider = 1 << width; - - return clamp(divider, 1, max_divider); -} - -static unsigned long audio_divider_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - struct clk_regmap *clk = to_clk_regmap(hw); - struct meson_clk_audio_div_data *adiv = meson_clk_audio_div_data(clk); - unsigned long divider; - - divider = meson_parm_read(clk->map, &adiv->div) + 1; - - return DIV_ROUND_UP_ULL((u64)parent_rate, divider); -} - -static long audio_divider_round_rate(struct clk_hw *hw, - unsigned long rate, - unsigned long *parent_rate) -{ - struct clk_regmap *clk = to_clk_regmap(hw); - struct meson_clk_audio_div_data *adiv = meson_clk_audio_div_data(clk); - unsigned long max_prate; - int divider; - - if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) { - divider = _div_round(*parent_rate, rate, adiv->flags); - divider = _valid_divider(adiv->div.width, divider); - return DIV_ROUND_UP_ULL((u64)*parent_rate, divider); - } - - /* Get the maximum parent rate */ - max_prate = clk_hw_round_rate(clk_hw_get_parent(hw), ULONG_MAX); - - /* Get the corresponding rounded down divider */ - divider = max_prate / rate; - divider = _valid_divider(adiv->div.width, divider); - - /* Get actual rate of the parent */ - *parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), - divider * rate); - - return DIV_ROUND_UP_ULL((u64)*parent_rate, divider); -} - -static int audio_divider_set_rate(struct clk_hw *hw, - unsigned long rate, - unsigned long parent_rate) -{ - struct clk_regmap *clk = to_clk_regmap(hw); - struct meson_clk_audio_div_data *adiv = meson_clk_audio_div_data(clk); - int val = _get_val(parent_rate, rate); - - meson_parm_write(clk->map, &adiv->div, val); - - return 0; -} - -const struct clk_ops meson_clk_audio_divider_ro_ops = { - .recalc_rate = audio_divider_recalc_rate, - .round_rate = audio_divider_round_rate, -}; - -const struct clk_ops meson_clk_audio_divider_ops = { - .recalc_rate = audio_divider_recalc_rate, - .round_rate = audio_divider_round_rate, - .set_rate = audio_divider_set_rate, -}; diff --git a/drivers/clk/meson/clk-phase.c b/drivers/clk/meson/clk-phase.c new file mode 100644 index 000000000000..cba43748ce3d --- /dev/null +++ b/drivers/clk/meson/clk-phase.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Copyright (c) 2018 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@baylibre.com> + */ + +#include <linux/clk-provider.h> +#include "clkc.h" + +#define phase_step(_width) (360 / (1 << (_width))) + +static inline struct meson_clk_phase_data * +meson_clk_phase_data(struct clk_regmap *clk) +{ + return (struct meson_clk_phase_data *)clk->data; +} + +int meson_clk_degrees_from_val(unsigned int val, unsigned int width) +{ + return phase_step(width) * val; +} +EXPORT_SYMBOL_GPL(meson_clk_degrees_from_val); + +unsigned int meson_clk_degrees_to_val(int degrees, unsigned int width) +{ + unsigned int val = DIV_ROUND_CLOSEST(degrees, phase_step(width)); + + /* + * This last calculation is here for cases when degrees is rounded + * to 360, in which case val == (1 << width). + */ + return val % (1 << width); +} +EXPORT_SYMBOL_GPL(meson_clk_degrees_to_val); + +static int meson_clk_phase_get_phase(struct clk_hw *hw) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_clk_phase_data *phase = meson_clk_phase_data(clk); + unsigned int val; + + val = meson_parm_read(clk->map, &phase->ph); + + return meson_clk_degrees_from_val(val, phase->ph.width); +} + +static int meson_clk_phase_set_phase(struct clk_hw *hw, int degrees) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_clk_phase_data *phase = meson_clk_phase_data(clk); + unsigned int val; + + val = meson_clk_degrees_to_val(degrees, phase->ph.width); + meson_parm_write(clk->map, &phase->ph, val); + + return 0; +} + +const struct clk_ops meson_clk_phase_ops = { + .get_phase = meson_clk_phase_get_phase, + .set_phase = meson_clk_phase_set_phase, +}; +EXPORT_SYMBOL_GPL(meson_clk_phase_ops); diff --git a/drivers/clk/meson/clk-triphase.c b/drivers/clk/meson/clk-triphase.c new file mode 100644 index 000000000000..4a59936251e5 --- /dev/null +++ b/drivers/clk/meson/clk-triphase.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Copyright (c) 2018 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@baylibre.com> + */ + +#include <linux/clk-provider.h> +#include "clkc-audio.h" + +/* + * This is a special clock for the audio controller. + * The phase of mst_sclk clock output can be controlled independently + * for the outside world (ph0), the tdmout (ph1) and tdmin (ph2). + * Controlling these 3 phases as just one makes things simpler and + * give the same clock view to all the element on the i2s bus. + * If necessary, we can still control the phase in the tdm block + * which makes these independent control redundant. + */ +static inline struct meson_clk_triphase_data * +meson_clk_triphase_data(struct clk_regmap *clk) +{ + return (struct meson_clk_triphase_data *)clk->data; +} + +static void meson_clk_triphase_sync(struct clk_hw *hw) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk); + unsigned int val; + + /* Get phase 0 and sync it to phase 1 and 2 */ + val = meson_parm_read(clk->map, &tph->ph0); + meson_parm_write(clk->map, &tph->ph1, val); + meson_parm_write(clk->map, &tph->ph2, val); +} + +static int meson_clk_triphase_get_phase(struct clk_hw *hw) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk); + unsigned int val; + + /* Phase are in sync, reading phase 0 is enough */ + val = meson_parm_read(clk->map, &tph->ph0); + + return meson_clk_degrees_from_val(val, tph->ph0.width); +} + +static int meson_clk_triphase_set_phase(struct clk_hw *hw, int degrees) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk); + unsigned int val; + + val = meson_clk_degrees_to_val(degrees, tph->ph0.width); + meson_parm_write(clk->map, &tph->ph0, val); + meson_parm_write(clk->map, &tph->ph1, val); + meson_parm_write(clk->map, &tph->ph2, val); + + return 0; +} + +const struct clk_ops meson_clk_triphase_ops = { + .init = meson_clk_triphase_sync, + .get_phase = meson_clk_triphase_get_phase, + .set_phase = meson_clk_triphase_set_phase, +}; +EXPORT_SYMBOL_GPL(meson_clk_triphase_ops); diff --git a/drivers/clk/meson/clkc-audio.h b/drivers/clk/meson/clkc-audio.h new file mode 100644 index 000000000000..0a7c157ebf81 --- /dev/null +++ b/drivers/clk/meson/clkc-audio.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@baylibre.com> + */ + +#ifndef __MESON_CLKC_AUDIO_H +#define __MESON_CLKC_AUDIO_H + +#include "clkc.h" + +struct meson_clk_triphase_data { + struct parm ph0; + struct parm ph1; + struct parm ph2; +}; + +struct meson_sclk_div_data { + struct parm div; + struct parm hi; + unsigned int cached_div; + struct clk_duty cached_duty; +}; + +extern const struct clk_ops meson_clk_triphase_ops; +extern const struct clk_ops meson_sclk_div_ops; + +#endif /* __MESON_CLKC_AUDIO_H */ diff --git a/drivers/clk/meson/clkc.h b/drivers/clk/meson/clkc.h index 2fb084330ee9..24cec16b6038 100644 --- a/drivers/clk/meson/clkc.h +++ b/drivers/clk/meson/clkc.h @@ -91,11 +91,13 @@ struct meson_clk_mpll_data { #define CLK_MESON_MPLL_ROUND_CLOSEST BIT(0) -struct meson_clk_audio_div_data { - struct parm div; - u8 flags; +struct meson_clk_phase_data { + struct parm ph; }; +int meson_clk_degrees_from_val(unsigned int val, unsigned int width); +unsigned int meson_clk_degrees_to_val(int degrees, unsigned int width); + #define MESON_GATE(_name, _reg, _bit) \ struct clk_regmap _name = { \ .data = &(struct clk_regmap_gate_data){ \ @@ -117,7 +119,6 @@ extern const struct clk_ops meson_clk_pll_ops; extern const struct clk_ops meson_clk_cpu_ops; extern const struct clk_ops meson_clk_mpll_ro_ops; extern const struct clk_ops meson_clk_mpll_ops; -extern const struct clk_ops meson_clk_audio_divider_ro_ops; -extern const struct clk_ops meson_clk_audio_divider_ops; +extern const struct clk_ops meson_clk_phase_ops; #endif /* __CLKC_H */ diff --git a/drivers/clk/meson/gxbb.c b/drivers/clk/meson/gxbb.c index 177fffb9ebef..86d3ae58e84c 100644 --- a/drivers/clk/meson/gxbb.c +++ b/drivers/clk/meson/gxbb.c @@ -7,7 +7,6 @@ #include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/init.h> -#include <linux/of_address.h> #include <linux/of_device.h> #include <linux/mfd/syscon.h> #include <linux/platform_device.h> @@ -971,28 +970,26 @@ static struct clk_regmap gxbb_cts_amclk_sel = { .mask = 0x3, .shift = 9, .table = (u32[]){ 1, 2, 3 }, + .flags = CLK_MUX_ROUND_CLOSEST, }, .hw.init = &(struct clk_init_data){ .name = "cts_amclk_sel", .ops = &clk_regmap_mux_ops, .parent_names = (const char *[]){ "mpll0", "mpll1", "mpll2" }, .num_parents = 3, - .flags = CLK_SET_RATE_PARENT, }, }; static struct clk_regmap gxbb_cts_amclk_div = { - .data = &(struct meson_clk_audio_div_data){ - .div = { - .reg_off = HHI_AUD_CLK_CNTL, - .shift = 0, - .width = 8, - }, + .data = &(struct clk_regmap_div_data) { + .offset = HHI_AUD_CLK_CNTL, + .shift = 0, + .width = 8, .flags = CLK_DIVIDER_ROUND_CLOSEST, }, .hw.init = &(struct clk_init_data){ .name = "cts_amclk_div", - .ops = &meson_clk_audio_divider_ops, + .ops = &clk_regmap_divider_ops, .parent_names = (const char *[]){ "cts_amclk_sel" }, .num_parents = 1, .flags = CLK_SET_RATE_PARENT, @@ -1019,13 +1016,13 @@ static struct clk_regmap gxbb_cts_mclk_i958_sel = { .mask = 0x3, .shift = 25, .table = (u32[]){ 1, 2, 3 }, + .flags = CLK_MUX_ROUND_CLOSEST, }, .hw.init = &(struct clk_init_data) { .name = "cts_mclk_i958_sel", .ops = &clk_regmap_mux_ops, .parent_names = (const char *[]){ "mpll0", "mpll1", "mpll2" }, .num_parents = 3, - .flags = CLK_SET_RATE_PARENT, }, }; @@ -1627,6 +1624,63 @@ static struct clk_regmap gxbb_vdec_hevc = { }, }; +static u32 mux_table_gen_clk[] = { 0, 4, 5, 6, 7, 8, + 9, 10, 11, 13, 14, }; +static const char * const gen_clk_parent_names[] = { + "xtal", "vdec_1", "vdec_hevc", "mpll0", "mpll1", "mpll2", + "fclk_div4", "fclk_div3", "fclk_div5", "fclk_div7", "gp0_pll", +}; + +static struct clk_regmap gxbb_gen_clk_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HHI_GEN_CLK_CNTL, + .mask = 0xf, + .shift = 12, + .table = mux_table_gen_clk, + }, + .hw.init = &(struct clk_init_data){ + .name = "gen_clk_sel", + .ops = &clk_regmap_mux_ops, + /* + * bits 15:12 selects from 14 possible parents: + * xtal, [rtc_oscin_i], [sys_cpu_div16], [ddr_dpll_pt], + * vid_pll, vid2_pll (hevc), mpll0, mpll1, mpll2, fdiv4, + * fdiv3, fdiv5, [cts_msr_clk], fdiv7, gp0_pll + */ + .parent_names = gen_clk_parent_names, + .num_parents = ARRAY_SIZE(gen_clk_parent_names), + }, +}; + +static struct clk_regmap gxbb_gen_clk_div = { + .data = &(struct clk_regmap_div_data){ + .offset = HHI_GEN_CLK_CNTL, + .shift = 0, + .width = 11, + }, + .hw.init = &(struct clk_init_data){ + .name = "gen_clk_div", + .ops = &clk_regmap_divider_ops, + .parent_names = (const char *[]){ "gen_clk_sel" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap gxbb_gen_clk = { + .data = &(struct clk_regmap_gate_data){ + .offset = HHI_GEN_CLK_CNTL, + .bit_idx = 7, + }, + .hw.init = &(struct clk_init_data){ + .name = "gen_clk", + .ops = &clk_regmap_gate_ops, + .parent_names = (const char *[]){ "gen_clk_div" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + /* Everything Else (EE) domain gates */ static MESON_GATE(gxbb_ddr, HHI_GCLK_MPEG0, 0); static MESON_GATE(gxbb_dos, HHI_GCLK_MPEG0, 1); @@ -1876,6 +1930,9 @@ static struct clk_hw_onecell_data gxbb_hw_onecell_data = { [CLKID_VDEC_HEVC_SEL] = &gxbb_vdec_hevc_sel.hw, [CLKID_VDEC_HEVC_DIV] = &gxbb_vdec_hevc_div.hw, [CLKID_VDEC_HEVC] = &gxbb_vdec_hevc.hw, + [CLKID_GEN_CLK_SEL] = &gxbb_gen_clk_sel.hw, + [CLKID_GEN_CLK_DIV] = &gxbb_gen_clk_div.hw, + [CLKID_GEN_CLK] = &gxbb_gen_clk.hw, [NR_CLKS] = NULL, }, .num = NR_CLKS, @@ -2038,6 +2095,9 @@ static struct clk_hw_onecell_data gxl_hw_onecell_data = { [CLKID_VDEC_HEVC_SEL] = &gxbb_vdec_hevc_sel.hw, [CLKID_VDEC_HEVC_DIV] = &gxbb_vdec_hevc_div.hw, [CLKID_VDEC_HEVC] = &gxbb_vdec_hevc.hw, + [CLKID_GEN_CLK_SEL] = &gxbb_gen_clk_sel.hw, + [CLKID_GEN_CLK_DIV] = &gxbb_gen_clk_div.hw, + [CLKID_GEN_CLK] = &gxbb_gen_clk.hw, [NR_CLKS] = NULL, }, .num = NR_CLKS, @@ -2202,6 +2262,9 @@ static struct clk_regmap *const gx_clk_regmaps[] = { &gxbb_vdec_hevc_sel, &gxbb_vdec_hevc_div, &gxbb_vdec_hevc, + &gxbb_gen_clk_sel, + &gxbb_gen_clk_div, + &gxbb_gen_clk, }; struct clkc_data { @@ -2228,17 +2291,9 @@ static const struct of_device_id clkc_match_table[] = { {}, }; -static const struct regmap_config clkc_regmap_config = { - .reg_bits = 32, - .val_bits = 32, - .reg_stride = 4, -}; - static int gxbb_clkc_probe(struct platform_device *pdev) { const struct clkc_data *clkc_data; - struct resource *res; - void __iomem *clk_base; struct regmap *map; int ret, i; struct device *dev = &pdev->dev; @@ -2250,31 +2305,8 @@ static int gxbb_clkc_probe(struct platform_device *pdev) /* Get the hhi system controller node if available */ map = syscon_node_to_regmap(of_get_parent(dev->of_node)); if (IS_ERR(map)) { - dev_err(dev, - "failed to get HHI regmap - Trying obsolete regs\n"); - - /* - * FIXME: HHI registers should be accessed through - * the appropriate system controller. This is required because - * there is more than just clocks in this register space - * - * This fallback method is only provided temporarily until - * all the platform DTs are properly using the syscon node - */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -EINVAL; - - clk_base = devm_ioremap(dev, res->start, resource_size(res)); - if (!clk_base) { - dev_err(dev, "Unable to map clk base\n"); - return -ENXIO; - } - - map = devm_regmap_init_mmio(dev, clk_base, - &clkc_regmap_config); - if (IS_ERR(map)) - return PTR_ERR(map); + dev_err(dev, "failed to get HHI regmap\n"); + return PTR_ERR(map); } /* Populate regmap for the common regmap backed clocks */ diff --git a/drivers/clk/meson/gxbb.h b/drivers/clk/meson/gxbb.h index ec1a812bf1fd..20dfb1daf5b8 100644 --- a/drivers/clk/meson/gxbb.h +++ b/drivers/clk/meson/gxbb.h @@ -66,7 +66,6 @@ #define HHI_USB_CLK_CNTL 0x220 /* 0x88 offset in data sheet */ #define HHI_32K_CLK_CNTL 0x224 /* 0x89 offset in data sheet */ #define HHI_GEN_CLK_CNTL 0x228 /* 0x8a offset in data sheet */ -#define HHI_GEN_CLK_CNTL 0x228 /* 0x8a offset in data sheet */ #define HHI_PCM_CLK_CNTL 0x258 /* 0x96 offset in data sheet */ #define HHI_NAND_CLK_CNTL 0x25C /* 0x97 offset in data sheet */ @@ -158,8 +157,10 @@ #define CLKID_VDEC_1_DIV 152 #define CLKID_VDEC_HEVC_SEL 154 #define CLKID_VDEC_HEVC_DIV 155 +#define CLKID_GEN_CLK_SEL 157 +#define CLKID_GEN_CLK_DIV 158 -#define NR_CLKS 157 +#define NR_CLKS 160 /* include the CLKIDs that have been made part of the DT binding */ #include <dt-bindings/clock/gxbb-clkc.h> diff --git a/drivers/clk/meson/sclk-div.c b/drivers/clk/meson/sclk-div.c new file mode 100644 index 000000000000..bc64019b8eeb --- /dev/null +++ b/drivers/clk/meson/sclk-div.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Copyright (c) 2018 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@baylibre.com> + * + * Sample clock generator divider: + * This HW divider gates with value 0 but is otherwise a zero based divider: + * + * val >= 1 + * divider = val + 1 + * + * The duty cycle may also be set for the LR clock variant. The duty cycle + * ratio is: + * + * hi = [0 - val] + * duty_cycle = (1 + hi) / (1 + val) + */ + +#include "clkc-audio.h" + +static inline struct meson_sclk_div_data * +meson_sclk_div_data(struct clk_regmap *clk) +{ + return (struct meson_sclk_div_data *)clk->data; +} + +static int sclk_div_maxval(struct meson_sclk_div_data *sclk) +{ + return (1 << sclk->div.width) - 1; +} + +static int sclk_div_maxdiv(struct meson_sclk_div_data *sclk) +{ + return sclk_div_maxval(sclk) + 1; +} + +static int sclk_div_getdiv(struct clk_hw *hw, unsigned long rate, + unsigned long prate, int maxdiv) +{ + int div = DIV_ROUND_CLOSEST_ULL((u64)prate, rate); + + return clamp(div, 2, maxdiv); +} + +static int sclk_div_bestdiv(struct clk_hw *hw, unsigned long rate, + unsigned long *prate, + struct meson_sclk_div_data *sclk) +{ + struct clk_hw *parent = clk_hw_get_parent(hw); + int bestdiv = 0, i; + unsigned long maxdiv, now, parent_now; + unsigned long best = 0, best_parent = 0; + + if (!rate) + rate = 1; + + maxdiv = sclk_div_maxdiv(sclk); + + if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) + return sclk_div_getdiv(hw, rate, *prate, maxdiv); + + /* + * The maximum divider we can use without overflowing + * unsigned long in rate * i below + */ + maxdiv = min(ULONG_MAX / rate, maxdiv); + + for (i = 2; i <= maxdiv; i++) { + /* + * It's the most ideal case if the requested rate can be + * divided from parent clock without needing to change + * parent rate, so return the divider immediately. + */ + if (rate * i == *prate) + return i; + + parent_now = clk_hw_round_rate(parent, rate * i); + now = DIV_ROUND_UP_ULL((u64)parent_now, i); + + if (abs(rate - now) < abs(rate - best)) { + bestdiv = i; + best = now; + best_parent = parent_now; + } + } + + if (!bestdiv) + bestdiv = sclk_div_maxdiv(sclk); + else + *prate = best_parent; + + return bestdiv; +} + +static long sclk_div_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk); + int div; + + div = sclk_div_bestdiv(hw, rate, prate, sclk); + + return DIV_ROUND_UP_ULL((u64)*prate, div); +} + +static void sclk_apply_ratio(struct clk_regmap *clk, + struct meson_sclk_div_data *sclk) +{ + unsigned int hi = DIV_ROUND_CLOSEST(sclk->cached_div * + sclk->cached_duty.num, + sclk->cached_duty.den); + + if (hi) + hi -= 1; + + meson_parm_write(clk->map, &sclk->hi, hi); +} + +static int sclk_div_set_duty_cycle(struct clk_hw *hw, + struct clk_duty *duty) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk); + + if (MESON_PARM_APPLICABLE(&sclk->hi)) { + memcpy(&sclk->cached_duty, duty, sizeof(*duty)); + sclk_apply_ratio(clk, sclk); + } + + return 0; +} + +static int sclk_div_get_duty_cycle(struct clk_hw *hw, + struct clk_duty *duty) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk); + int hi; + + if (!MESON_PARM_APPLICABLE(&sclk->hi)) { + duty->num = 1; + duty->den = 2; + return 0; + } + + hi = meson_parm_read(clk->map, &sclk->hi); + duty->num = hi + 1; + duty->den = sclk->cached_div; + return 0; +} + +static void sclk_apply_divider(struct clk_regmap *clk, + struct meson_sclk_div_data *sclk) +{ + if (MESON_PARM_APPLICABLE(&sclk->hi)) + sclk_apply_ratio(clk, sclk); + + meson_parm_write(clk->map, &sclk->div, sclk->cached_div - 1); +} + +static int sclk_div_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long prate) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk); + unsigned long maxdiv = sclk_div_maxdiv(sclk); + + sclk->cached_div = sclk_div_getdiv(hw, rate, prate, maxdiv); + + if (clk_hw_is_enabled(hw)) + sclk_apply_divider(clk, sclk); + + return 0; +} + +static unsigned long sclk_div_recalc_rate(struct clk_hw *hw, + unsigned long prate) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk); + + return DIV_ROUND_UP_ULL((u64)prate, sclk->cached_div); +} + +static int sclk_div_enable(struct clk_hw *hw) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk); + + sclk_apply_divider(clk, sclk); + + return 0; +} + +static void sclk_div_disable(struct clk_hw *hw) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk); + + meson_parm_write(clk->map, &sclk->div, 0); +} + +static int sclk_div_is_enabled(struct clk_hw *hw) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk); + + if (meson_parm_read(clk->map, &sclk->div)) + return 1; + + return 0; +} + +static void sclk_div_init(struct clk_hw *hw) +{ + struct clk_regmap *clk = to_clk_regmap(hw); + struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk); + unsigned int val; + + val = meson_parm_read(clk->map, &sclk->div); + + /* if the divider is initially disabled, assume max */ + if (!val) + sclk->cached_div = sclk_div_maxdiv(sclk); + else + sclk->cached_div = val + 1; + + sclk_div_get_duty_cycle(hw, &sclk->cached_duty); +} + +const struct clk_ops meson_sclk_div_ops = { + .recalc_rate = sclk_div_recalc_rate, + .round_rate = sclk_div_round_rate, + .set_rate = sclk_div_set_rate, + .enable = sclk_div_enable, + .disable = sclk_div_disable, + .is_enabled = sclk_div_is_enabled, + .get_duty_cycle = sclk_div_get_duty_cycle, + .set_duty_cycle = sclk_div_set_duty_cycle, + .init = sclk_div_init, +}; +EXPORT_SYMBOL_GPL(meson_sclk_div_ops); |