diff mbox

[2/2] clk: zte: add audio clocks for zx296718

Message ID 1481189157-8995-2-git-send-email-shawnguo@kernel.org (mailing list archive)
State New, archived
Headers show

Commit Message

Shawn Guo Dec. 8, 2016, 9:25 a.m. UTC
From: Jun Nie <jun.nie@linaro.org>

The audio related clock support is missing from the existing zx296718
clock driver.  Let's add it, so that the upstream ZX SPDIF driver can
work for HDMI audio support.

Signed-off-by: Jun Nie <jun.nie@linaro.org>
Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
 drivers/clk/zte/clk-zx296718.c | 150 +++++++++++++++++++++++++++++++++++++++++
 drivers/clk/zte/clk.c          | 149 ++++++++++++++++++++++++++++++++++++++++
 drivers/clk/zte/clk.h          |  28 ++++++++
 3 files changed, 327 insertions(+)

Comments

Jun Nie Dec. 8, 2016, 2:55 p.m. UTC | #1
2016-12-08 17:25 GMT+08:00 Shawn Guo <shawnguo@kernel.org>:
> From: Jun Nie <jun.nie@linaro.org>
>
> The audio related clock support is missing from the existing zx296718
> clock driver.  Let's add it, so that the upstream ZX SPDIF driver can
> work for HDMI audio support.
>
> Signed-off-by: Jun Nie <jun.nie@linaro.org>
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> ---
>  drivers/clk/zte/clk-zx296718.c | 150 +++++++++++++++++++++++++++++++++++++++++
>  drivers/clk/zte/clk.c          | 149 ++++++++++++++++++++++++++++++++++++++++
>  drivers/clk/zte/clk.h          |  28 ++++++++
>  3 files changed, 327 insertions(+)
>
> diff --git a/drivers/clk/zte/clk-zx296718.c b/drivers/clk/zte/clk-zx296718.c
> index 707d62956e9b..eed8581b1b25 100644
> --- a/drivers/clk/zte/clk-zx296718.c
> +++ b/drivers/clk/zte/clk-zx296718.c
> @@ -888,10 +888,160 @@ static int __init lsp1_clocks_init(struct device_node *np)
>         return 0;
>  }
>
> +PNAME(audio_wclk_common_p) = {
> +       "audio_99m",
> +       "audio_24m",
> +};
> +
> +PNAME(audio_timer_p) = {
> +       "audio_24m",
> +       "audio_32k",
> +};
> +
> +static struct zx_clk_mux audio_mux_clk[] = {
> +       MUX(0, "i2s0_wclk_mux", audio_wclk_common_p, AUDIO_I2S0_CLK, 0, 1),
> +       MUX(0, "i2s1_wclk_mux", audio_wclk_common_p, AUDIO_I2S1_CLK, 0, 1),
> +       MUX(0, "i2s2_wclk_mux", audio_wclk_common_p, AUDIO_I2S2_CLK, 0, 1),
> +       MUX(0, "i2s3_wclk_mux", audio_wclk_common_p, AUDIO_I2S3_CLK, 0, 1),
> +       MUX(0, "i2c0_wclk_mux", audio_wclk_common_p, AUDIO_I2C0_CLK, 0, 1),
> +       MUX(0, "spdif0_wclk_mux", audio_wclk_common_p, AUDIO_SPDIF0_CLK, 0, 1),
> +       MUX(0, "spdif1_wclk_mux", audio_wclk_common_p, AUDIO_SPDIF1_CLK, 0, 1),
> +       MUX(0, "timer_wclk_mux", audio_timer_p, AUDIO_TIMER_CLK, 0, 1),
> +};
> +
> +struct zx_clk_audio_div_table i2s_wclk_div_table[] = {
> +       {2048000, 0x3000030, 0xffff5700},
> +       {4096000, 0x3000018, 0xffff2b80},
> +       {2822400, 0x3000011, 0xffff89cb},
> +       {3072000, 0x3000010, 0xffff1d00},
> +       {4096000, 0x300000c, 0xffff15c0},
> +       {5644800, 0x3000008, 0xffffc4e5},
> +       {6144000, 0x3000008, 0xffff0e80},
> +       {11289600, 0x3000004, 0xffff6273},
> +       {12288000, 0x3000004, 0xffff0740},
> +       {22579200, 0x3000002, 0xffff3139},
> +       {24576000, 0x3000002, 0xffff03a0},
> +};
> +
> +struct zx_clk_audio_div_table spdif_wclk_div_table[] = {
> +       {2822400, 0x00023, 0xffff1397},
> +       {3072000, 0x00020, 0xffff3a00},
> +       {4096000, 0x00018, 0xffff2b80},
> +       {5644800, 0x00011, 0xffff89cb},
> +       {6144000, 0x00010, 0xffff1d00},
> +       {11289600, 0x00008, 0xffffc4e5},
> +       {12288000, 0x00008, 0xffff0e80},
> +       {22579200, 0x00004, 0xffff6273},
> +       {24576000, 0x00004, 0xffff0740},
> +};

You can remove these two tables and table pointer member as I already
removed table pointer assignment in macro AUDIO_DIV in this code. I am
sorry for not cleaning code properly.

> +
> +struct clk_zx_audio_divider audio_adiv_clk[] = {
> +       AUDIO_DIV(0, "i2s0_wclk_div", "i2s0_wclk_mux", AUDIO_I2S0_DIV_CFG1, i2s_wclk_div_table),
> +       AUDIO_DIV(0, "i2s1_wclk_div", "i2s1_wclk_mux", AUDIO_I2S1_DIV_CFG1, i2s_wclk_div_table),
> +       AUDIO_DIV(0, "i2s2_wclk_div", "i2s2_wclk_mux", AUDIO_I2S2_DIV_CFG1, i2s_wclk_div_table),
> +       AUDIO_DIV(0, "i2s3_wclk_div", "i2s3_wclk_mux", AUDIO_I2S3_DIV_CFG1, i2s_wclk_div_table),
> +       AUDIO_DIV(0, "spdif0_wclk_div", "spdif0_wclk_mux", AUDIO_SPDIF0_DIV_CFG1, spdif_wclk_div_table),
> +       AUDIO_DIV(0, "spdif1_wclk_div", "spdif1_wclk_mux", AUDIO_SPDIF1_DIV_CFG1, spdif_wclk_div_table),
> +};
> +
> +struct zx_clk_div audio_div_clk[] = {
> +       DIV_T(0, "tdm_wclk_div", "audio_16m384", AUDIO_TDM_CLK, 8, 4, 0, common_div_table),
> +};
> +
> +struct zx_clk_gate audio_gate_clk[] = {
> +       GATE(AUDIO_I2S0_WCLK, "i2s0_wclk", "i2s0_wclk_div", AUDIO_I2S0_CLK, 9, CLK_SET_RATE_PARENT, 0),
> +       GATE(AUDIO_I2S1_WCLK, "i2s1_wclk", "i2s1_wclk_div", AUDIO_I2S1_CLK, 9, CLK_SET_RATE_PARENT, 0),
> +       GATE(AUDIO_I2S2_WCLK, "i2s2_wclk", "i2s2_wclk_div", AUDIO_I2S2_CLK, 9, CLK_SET_RATE_PARENT, 0),
> +       GATE(AUDIO_I2S3_WCLK, "i2s3_wclk", "i2s3_wclk_div", AUDIO_I2S3_CLK, 9, CLK_SET_RATE_PARENT, 0),
> +       GATE(AUDIO_I2C0_WCLK, "i2c0_wclk", "i2c0_wclk_mux", AUDIO_I2C0_CLK, 9, CLK_SET_RATE_PARENT, 0),
> +       GATE(AUDIO_SPDIF0_WCLK, "spdif0_wclk", "spdif0_wclk_div", AUDIO_SPDIF0_CLK, 9, CLK_SET_RATE_PARENT, 0),
> +       GATE(AUDIO_SPDIF1_WCLK, "spdif1_wclk", "spdif1_wclk_div", AUDIO_SPDIF1_CLK, 9, CLK_SET_RATE_PARENT, 0),
> +       GATE(AUDIO_TDM_WCLK, "tdm_wclk", "tdm_wclk_div", AUDIO_TDM_CLK, 17, CLK_SET_RATE_PARENT, 0),
> +       GATE(AUDIO_TS_PCLK, "tempsensor_pclk", "clk49m5", AUDIO_TS_CLK, 1, 0, 0),
> +};
> +
> +static struct clk_hw_onecell_data audio_hw_onecell_data = {
> +       .num = AUDIO_NR_CLKS,
> +       .hws = {
> +               [AUDIO_NR_CLKS - 1] = NULL,
> +       },
> +};
> +
> +static int __init audio_clocks_init(struct device_node *np)
> +{
> +       void __iomem *reg_base;
> +       int i, ret;
> +
> +       reg_base = of_iomap(np, 0);
> +       if (!reg_base) {
> +               pr_err("%s: Unable to map audio clk base\n", __func__);
> +               return -ENXIO;
> +       }
> +
> +       for (i = 0; i < ARRAY_SIZE(audio_mux_clk); i++) {
> +               if (audio_mux_clk[i].id)
> +                       audio_hw_onecell_data.hws[audio_mux_clk[i].id] =
> +                                       &audio_mux_clk[i].mux.hw;
> +
> +               audio_mux_clk[i].mux.reg += (u64)reg_base;

Fix build test failure on 32bit system.
 audio_mux_clk[i].mux.reg +=  (uintptr_t)reg_base;

> +               ret = clk_hw_register(NULL, &audio_mux_clk[i].mux.hw);
> +               if (ret) {
> +                       pr_warn("audio clk %s init error!\n",
> +                               audio_mux_clk[i].mux.hw.init->name);
> +               }
> +       }
> +
> +       for (i = 0; i < ARRAY_SIZE(audio_adiv_clk); i++) {
> +               if (audio_adiv_clk[i].id)
> +                       audio_hw_onecell_data.hws[audio_adiv_clk[i].id] =
> +                                       &audio_adiv_clk[i].hw;
> +
> +               audio_adiv_clk[i].reg_base += (u64)reg_base;

The same to this line and below cases.

> +               ret = clk_hw_register(NULL, &audio_adiv_clk[i].hw);
> +               if (ret) {
> +                       pr_warn("audio clk %s init error!\n",
> +                               audio_adiv_clk[i].hw.init->name);
> +               }
> +       }
> +
> +       for (i = 0; i < ARRAY_SIZE(audio_div_clk); i++) {
> +               if (audio_div_clk[i].id)
> +                       audio_hw_onecell_data.hws[audio_div_clk[i].id] =
> +                                       &audio_div_clk[i].div.hw;
> +
> +               audio_div_clk[i].div.reg += (u64)reg_base;
> +               ret = clk_hw_register(NULL, &audio_div_clk[i].div.hw);
> +               if (ret) {
> +                       pr_warn("audio clk %s init error!\n",
> +                               audio_div_clk[i].div.hw.init->name);
> +               }
> +       }
> +
> +       for (i = 0; i < ARRAY_SIZE(audio_gate_clk); i++) {
> +               if (audio_gate_clk[i].id)
> +                       audio_hw_onecell_data.hws[audio_gate_clk[i].id] =
> +                                       &audio_gate_clk[i].gate.hw;
> +
> +               audio_gate_clk[i].gate.reg += (u64)reg_base;
> +               ret = clk_hw_register(NULL, &audio_gate_clk[i].gate.hw);
> +               if (ret) {
> +                       pr_warn("audio clk %s init error!\n",
> +                               audio_gate_clk[i].gate.hw.init->name);
> +               }
> +       }
> +
> +       if (of_clk_add_hw_provider(np, of_clk_hw_onecell_get, &audio_hw_onecell_data))
> +               panic("could not register clk provider\n");
> +       pr_info("audio-clk init over, nr:%d\n", AUDIO_NR_CLKS);
> +
> +       return 0;
> +}
> +
>  static const struct of_device_id zx_clkc_match_table[] = {
>         { .compatible = "zte,zx296718-topcrm", .data = &top_clocks_init },
>         { .compatible = "zte,zx296718-lsp0crm", .data = &lsp0_clocks_init },
>         { .compatible = "zte,zx296718-lsp1crm", .data = &lsp1_clocks_init },
> +       { .compatible = "zte,zx296718-audiocrm", .data = &audio_clocks_init },
>         { }
>  };
>
> diff --git a/drivers/clk/zte/clk.c b/drivers/clk/zte/clk.c
> index c4c1251bc1e7..ea97024b37aa 100644
> --- a/drivers/clk/zte/clk.c
> +++ b/drivers/clk/zte/clk.c
> @@ -9,6 +9,7 @@
>
>  #include <linux/clk-provider.h>
>  #include <linux/err.h>
> +#include <linux/gcd.h>
>  #include <linux/io.h>
>  #include <linux/iopoll.h>
>  #include <linux/slab.h>
> @@ -310,3 +311,151 @@ struct clk *clk_register_zx_audio(const char *name,
>
>         return clk;
>  }
> +
> +#define CLK_AUDIO_DIV_FRAC     BIT(0)
> +#define CLK_AUDIO_DIV_INT      BIT(1)
> +#define CLK_AUDIO_DIV_UNCOMMON BIT(1)
> +
> +#define CLK_AUDIO_DIV_FRAC_NSHIFT      16
> +#define CLK_AUDIO_DIV_INT_FRAC_RE      BIT(16)
> +#define CLK_AUDIO_DIV_INT_FRAC_MAX     (0xffff)
> +#define CLK_AUDIO_DIV_INT_FRAC_MIN     (0x2)
> +#define CLK_AUDIO_DIV_INT_INT_SHIFT    24
> +#define CLK_AUDIO_DIV_INT_INT_WIDTH    4
> +
> +#define to_clk_zx_audio_div(_hw) container_of(_hw, struct clk_zx_audio_divider, hw)
> +
> +static unsigned long audio_calc_rate(struct clk_zx_audio_divider *audio_div,
> +                                    u32 reg_frac, u32 reg_int,
> +                                    unsigned long parent_rate)
> +{
> +       unsigned long rate, m, n;
> +
> +       if (audio_div->table) {
> +               const struct zx_clk_audio_div_table *divt = audio_div->table;
> +
> +               for (; divt->rate; divt++) {
> +                       if ((divt->int_reg == reg_int) && (divt->frac_reg == reg_frac))
> +                               return divt->rate;
> +               }
> +       }
> +       if (audio_div->table)
> +               pr_warn("cannot found the config(int_reg:0x%x, frac_reg:0x%x) in table, we will caculate it\n",
> +                       reg_int, reg_frac);

Logic of register value table can be removed now.

> +
> +       m = reg_frac & 0xffff;
> +       n = (reg_frac >> 16) & 0xffff;
> +
> +       m = (reg_int & 0xffff) * n + m;
> +       rate = (parent_rate * n) / m;
> +
> +       return rate;
> +}
> +
> +static void audio_calc_reg(struct clk_zx_audio_divider *audio_div,
> +                          struct zx_clk_audio_div_table *div_table,
> +                          unsigned long rate, unsigned long parent_rate)
> +{
> +       unsigned int reg_int, reg_frac;
> +       unsigned long m, n, div;
> +
> +       if (audio_div->table) {
> +               const struct zx_clk_audio_div_table *divt = audio_div->table;
> +
> +               for (; divt->rate; divt++) {
> +                       if (divt->rate == rate) {
> +                               div_table->rate = divt->rate;
> +                               div_table->int_reg = divt->int_reg;
> +                               div_table->frac_reg = divt->frac_reg;
> +                               return;
> +                       }
> +               }
> +       }
> +       if (audio_div->table)
> +               pr_warn("cannot found the rate(%ld) in table, we will caculate the config\n",
> +                       rate);

Table is not used here actually neither.

> +
> +       reg_int = parent_rate / rate;
> +
> +       if (reg_int > CLK_AUDIO_DIV_INT_FRAC_MAX)
> +               reg_int = CLK_AUDIO_DIV_INT_FRAC_MAX;
> +       else if (reg_int < CLK_AUDIO_DIV_INT_FRAC_MIN)
> +               reg_int = 0;
> +       m = parent_rate - rate * reg_int;
> +       n = rate;
> +
> +       div = gcd(m, n);
> +       m = m / div;
> +       n = n / div;
> +
> +       if ((m >> 16) || (n >> 16)) {
> +               if (m > n) {
> +                       n = n * 0xffff / m;
> +                       m = 0xffff;
> +               } else {
> +                       m = m * 0xffff / n;
> +                       n = 0xffff;
> +               }
> +       }
> +       reg_frac = m | (n << 16);
> +
> +       div_table->rate = (ulong)(parent_rate * n) / ((ulong)reg_int * n + m);
> +       div_table->int_reg = reg_int;
> +       div_table->frac_reg = reg_frac;
> +}
> +
> +static unsigned long zx_audio_div_recalc_rate(struct clk_hw *hw,
> +                                         unsigned long parent_rate)
> +{
> +       struct clk_zx_audio_divider *zx_audio_div = to_clk_zx_audio_div(hw);
> +       u32 reg_frac, reg_int;
> +
> +       reg_frac = readl_relaxed(zx_audio_div->reg_base);
> +       reg_int = readl_relaxed(zx_audio_div->reg_base + 0x4);
> +
> +       return audio_calc_rate(zx_audio_div, reg_frac, reg_int, parent_rate);
> +}
> +
> +static long zx_audio_div_round_rate(struct clk_hw *hw, unsigned long rate,
> +                               unsigned long *prate)
> +{
> +       struct clk_zx_audio_divider *zx_audio_div = to_clk_zx_audio_div(hw);
> +       struct zx_clk_audio_div_table divt;
> +
> +       audio_calc_reg(zx_audio_div, &divt, rate, *prate);
> +
> +       return audio_calc_rate(zx_audio_div, divt.frac_reg, divt.int_reg, *prate);
> +}
> +
> +static int zx_audio_div_set_rate(struct clk_hw *hw, unsigned long rate,
> +                                   unsigned long parent_rate)
> +{
> +       struct clk_zx_audio_divider *zx_audio_div = to_clk_zx_audio_div(hw);
> +       struct zx_clk_audio_div_table divt;
> +       unsigned int val;
> +
> +       audio_calc_reg(zx_audio_div, &divt, rate, parent_rate);
> +       if (divt.rate != rate)
> +               pr_info("the real rate is:%ld", divt.rate);
> +
> +       writel_relaxed(divt.frac_reg, zx_audio_div->reg_base);
> +
> +       val = readl_relaxed(zx_audio_div->reg_base + 0x4);
> +       val &= ~0xffff;
> +       val |= divt.int_reg | CLK_AUDIO_DIV_INT_FRAC_RE;
> +       writel_relaxed(val, zx_audio_div->reg_base + 0x4);
> +
> +       mdelay(1);
> +
> +       val = readl_relaxed(zx_audio_div->reg_base + 0x4);
> +       val &= ~CLK_AUDIO_DIV_INT_FRAC_RE;
> +       writel_relaxed(val, zx_audio_div->reg_base + 0x4);
> +
> +       return 0;
> +}
> +
> +const struct clk_ops zx_audio_div_ops = {
> +       .recalc_rate = zx_audio_div_recalc_rate,
> +       .round_rate = zx_audio_div_round_rate,
> +       .set_rate = zx_audio_div_set_rate,
> +};
> diff --git a/drivers/clk/zte/clk.h b/drivers/clk/zte/clk.h
> index 0df3474b2cf3..6e7ccb752c24 100644
> --- a/drivers/clk/zte/clk.h
> +++ b/drivers/clk/zte/clk.h
> @@ -153,6 +153,32 @@ struct zx_clk_div {
>         .id = _id,                                                      \
>  }
>
> +struct zx_clk_audio_div_table {
> +       unsigned long rate;
> +       unsigned int int_reg;
> +       unsigned int frac_reg;
> +};
> +
> +struct clk_zx_audio_divider {
> +       struct clk_hw                           hw;
> +       void __iomem                            *reg_base;
> +       const struct zx_clk_audio_div_table     *table;
> +       unsigned int                            rate_count;
> +       spinlock_t                              *lock;
> +       u16                                     id;
> +};
> +
> +#define AUDIO_DIV(_id, _name, _parent, _reg, _table)                   \

Remove unused table here.

> +{                                                                      \
> +       .reg_base       = (void __iomem *) _reg,                        \
> +       .lock           = &clk_lock,                                    \
> +       .hw.init        = CLK_HW_INIT(_name,                            \
> +                                     _parent,                          \
> +                                     &zx_audio_div_ops,                \
> +                                     0),                               \
> +       .id = _id,                                                      \
> +}
> +
>  struct clk *clk_register_zx_pll(const char *name, const char *parent_name,
>         unsigned long flags, void __iomem *reg_base,
>         const struct zx_pll_config *lookup_table, int count, spinlock_t *lock);
> @@ -167,4 +193,6 @@ struct clk *clk_register_zx_audio(const char *name,
>                                   unsigned long flags, void __iomem *reg_base);
>
>  extern const struct clk_ops zx_pll_ops;
> +extern const struct clk_ops zx_audio_div_ops;
> +
>  #endif
> --
> 1.9.1
>
Stephen Boyd Dec. 8, 2016, 9:04 p.m. UTC | #2
On 12/08, Shawn Guo wrote:
> +
> +static int __init audio_clocks_init(struct device_node *np)
> +{
> +	void __iomem *reg_base;
> +	int i, ret;
> +
> +	reg_base = of_iomap(np, 0);
> +	if (!reg_base) {
> +		pr_err("%s: Unable to map audio clk base\n", __func__);
> +		return -ENXIO;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(audio_mux_clk); i++) {
> +		if (audio_mux_clk[i].id)
> +			audio_hw_onecell_data.hws[audio_mux_clk[i].id] =
> +					&audio_mux_clk[i].mux.hw;
> +
> +		audio_mux_clk[i].mux.reg += (u64)reg_base;
> +		ret = clk_hw_register(NULL, &audio_mux_clk[i].mux.hw);
> +		if (ret) {
> +			pr_warn("audio clk %s init error!\n",
> +				audio_mux_clk[i].mux.hw.init->name);
> +		}
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(audio_adiv_clk); i++) {
> +		if (audio_adiv_clk[i].id)
> +			audio_hw_onecell_data.hws[audio_adiv_clk[i].id] =
> +					&audio_adiv_clk[i].hw;
> +
> +		audio_adiv_clk[i].reg_base += (u64)reg_base;
> +		ret = clk_hw_register(NULL, &audio_adiv_clk[i].hw);
> +		if (ret) {
> +			pr_warn("audio clk %s init error!\n",
> +				audio_adiv_clk[i].hw.init->name);
> +		}
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(audio_div_clk); i++) {
> +		if (audio_div_clk[i].id)
> +			audio_hw_onecell_data.hws[audio_div_clk[i].id] =
> +					&audio_div_clk[i].div.hw;
> +
> +		audio_div_clk[i].div.reg += (u64)reg_base;
> +		ret = clk_hw_register(NULL, &audio_div_clk[i].div.hw);
> +		if (ret) {
> +			pr_warn("audio clk %s init error!\n",
> +				audio_div_clk[i].div.hw.init->name);
> +		}
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(audio_gate_clk); i++) {
> +		if (audio_gate_clk[i].id)
> +			audio_hw_onecell_data.hws[audio_gate_clk[i].id] =
> +					&audio_gate_clk[i].gate.hw;
> +
> +		audio_gate_clk[i].gate.reg += (u64)reg_base;
> +		ret = clk_hw_register(NULL, &audio_gate_clk[i].gate.hw);
> +		if (ret) {
> +			pr_warn("audio clk %s init error!\n",
> +				audio_gate_clk[i].gate.hw.init->name);
> +		}
> +	}
> +
> +	if (of_clk_add_hw_provider(np, of_clk_hw_onecell_get, &audio_hw_onecell_data))
> +		panic("could not register clk provider\n");

Why don't we return error? We returned errors before if we
couldn't map the ioregion.

> +	pr_info("audio-clk init over, nr:%d\n", AUDIO_NR_CLKS);

debug noise?

> +
> +	return 0;
> +}
> +
>  static const struct of_device_id zx_clkc_match_table[] = {
>  	{ .compatible = "zte,zx296718-topcrm", .data = &top_clocks_init },
>  	{ .compatible = "zte,zx296718-lsp0crm", .data = &lsp0_clocks_init },
>  	{ .compatible = "zte,zx296718-lsp1crm", .data = &lsp1_clocks_init },
> +	{ .compatible = "zte,zx296718-audiocrm", .data = &audio_clocks_init },
>  	{ }
>  };
>  
> diff --git a/drivers/clk/zte/clk.c b/drivers/clk/zte/clk.c
> index c4c1251bc1e7..ea97024b37aa 100644
> --- a/drivers/clk/zte/clk.c
> +++ b/drivers/clk/zte/clk.c
> @@ -9,6 +9,7 @@
>  
>  #include <linux/clk-provider.h>
>  #include <linux/err.h>
> +#include <linux/gcd.h>
>  #include <linux/io.h>
>  #include <linux/iopoll.h>
>  #include <linux/slab.h>
> @@ -310,3 +311,151 @@ struct clk *clk_register_zx_audio(const char *name,
>  
>  	return clk;
>  }
> +
> +#define CLK_AUDIO_DIV_FRAC	BIT(0)
> +#define CLK_AUDIO_DIV_INT	BIT(1)
> +#define CLK_AUDIO_DIV_UNCOMMON	BIT(1)
> +
> +#define CLK_AUDIO_DIV_FRAC_NSHIFT	16
> +#define CLK_AUDIO_DIV_INT_FRAC_RE	BIT(16)
> +#define CLK_AUDIO_DIV_INT_FRAC_MAX	(0xffff)
> +#define CLK_AUDIO_DIV_INT_FRAC_MIN	(0x2)
> +#define CLK_AUDIO_DIV_INT_INT_SHIFT	24
> +#define CLK_AUDIO_DIV_INT_INT_WIDTH	4
> +
> +#define to_clk_zx_audio_div(_hw) container_of(_hw, struct clk_zx_audio_divider, hw)
> +
> +static unsigned long audio_calc_rate(struct clk_zx_audio_divider *audio_div,
> +				     u32 reg_frac, u32 reg_int,
> +				     unsigned long parent_rate)
> +{
> +	unsigned long rate, m, n;
> +
> +	if (audio_div->table) {
> +		const struct zx_clk_audio_div_table *divt = audio_div->table;
> +
> +		for (; divt->rate; divt++) {
> +			if ((divt->int_reg == reg_int) && (divt->frac_reg == reg_frac))

Please remove extra parenthesis here.

> +				return divt->rate;
> +		}
> +	}
> +	if (audio_div->table)
> +		pr_warn("cannot found the config(int_reg:0x%x, frac_reg:0x%x) in table, we will caculate it\n",
> +			reg_int, reg_frac);
> +
> +	m = reg_frac & 0xffff;
> +	n = (reg_frac >> 16) & 0xffff;
> +
> +	m = (reg_int & 0xffff) * n + m;
> +	rate = (parent_rate * n) / m;
> +
> +	return rate;
> +}
> +
> +static void audio_calc_reg(struct clk_zx_audio_divider *audio_div,
> +			   struct zx_clk_audio_div_table *div_table,
> +			   unsigned long rate, unsigned long parent_rate)
> +{
> +	unsigned int reg_int, reg_frac;
> +	unsigned long m, n, div;
> +
> +	if (audio_div->table) {
> +		const struct zx_clk_audio_div_table *divt = audio_div->table;
> +
> +		for (; divt->rate; divt++) {
> +			if (divt->rate == rate) {
> +				div_table->rate = divt->rate;
> +				div_table->int_reg = divt->int_reg;
> +				div_table->frac_reg = divt->frac_reg;
> +				return;
> +			}
> +		}
> +	}
> +	if (audio_div->table)
> +		pr_warn("cannot found the rate(%ld) in table, we will caculate the config\n",
> +			rate);
> +
> +	reg_int = parent_rate / rate;
> +
> +	if (reg_int > CLK_AUDIO_DIV_INT_FRAC_MAX)
> +		reg_int = CLK_AUDIO_DIV_INT_FRAC_MAX;
> +	else if (reg_int < CLK_AUDIO_DIV_INT_FRAC_MIN)
> +		reg_int = 0;
> +	m = parent_rate - rate * reg_int;
> +	n = rate;
> +
> +	div = gcd(m, n);
> +	m = m / div;
> +	n = n / div;
> +
> +	if ((m >> 16) || (n >> 16)) {
> +		if (m > n) {
> +			n = n * 0xffff / m;
> +			m = 0xffff;
> +		} else {
> +			m = m * 0xffff / n;
> +			n = 0xffff;
> +		}
> +	}
> +	reg_frac = m | (n << 16);
> +
> +	div_table->rate = (ulong)(parent_rate * n) / ((ulong)reg_int * n + m);

Please don't use ulong, use unsigned long. Also consider using
local variables so the line isn't overly long.

> +	div_table->int_reg = reg_int;
> +	div_table->frac_reg = reg_frac;
> +}
[...]
> +
> +static int zx_audio_div_set_rate(struct clk_hw *hw, unsigned long rate,
> +				    unsigned long parent_rate)
> +{
> +	struct clk_zx_audio_divider *zx_audio_div = to_clk_zx_audio_div(hw);
> +	struct zx_clk_audio_div_table divt;
> +	unsigned int val;
> +
> +	audio_calc_reg(zx_audio_div, &divt, rate, parent_rate);
> +	if (divt.rate != rate)
> +		pr_info("the real rate is:%ld", divt.rate);

Debug noise?

> +
> +	writel_relaxed(divt.frac_reg, zx_audio_div->reg_base);
> +
> +	val = readl_relaxed(zx_audio_div->reg_base + 0x4);
> +	val &= ~0xffff;
> +	val |= divt.int_reg | CLK_AUDIO_DIV_INT_FRAC_RE;
> +	writel_relaxed(val, zx_audio_div->reg_base + 0x4);
> +
> +	mdelay(1);
> +
> +	val = readl_relaxed(zx_audio_div->reg_base + 0x4);
> +	val &= ~CLK_AUDIO_DIV_INT_FRAC_RE;
> +	writel_relaxed(val, zx_audio_div->reg_base + 0x4);
> +
> +	return 0;
> +}
> +
> +const struct clk_ops zx_audio_div_ops = {
> +	.recalc_rate = zx_audio_div_recalc_rate,
> +	.round_rate = zx_audio_div_round_rate,
> +	.set_rate = zx_audio_div_set_rate,
> +};
>
Shawn Guo Dec. 16, 2016, 6:40 a.m. UTC | #3
On Thu, Dec 08, 2016 at 01:04:53PM -0800, Stephen Boyd wrote:
> > +	if (of_clk_add_hw_provider(np, of_clk_hw_onecell_get, &audio_hw_onecell_data))
> > +		panic("could not register clk provider\n");
> 
> Why don't we return error? We returned errors before if we
> couldn't map the ioregion.

Yes.  Rather than panic, we should check return and give an error
message in case of failure.

> 
> > +	pr_info("audio-clk init over, nr:%d\n", AUDIO_NR_CLKS);
> 
> debug noise?

Agreed.  I will just clean it up.

<snip>

> > +static unsigned long audio_calc_rate(struct clk_zx_audio_divider *audio_div,
> > +				     u32 reg_frac, u32 reg_int,
> > +				     unsigned long parent_rate)
> > +{
> > +	unsigned long rate, m, n;
> > +
> > +	if (audio_div->table) {
> > +		const struct zx_clk_audio_div_table *divt = audio_div->table;
> > +
> > +		for (; divt->rate; divt++) {
> > +			if ((divt->int_reg == reg_int) && (divt->frac_reg == reg_frac))
> 
> Please remove extra parenthesis here.

The whole hunk of the code will be dropped, since the divider lookup
table is not actually used.

> 
> > +				return divt->rate;
> > +		}
> > +	}
> > +	if (audio_div->table)
> > +		pr_warn("cannot found the config(int_reg:0x%x, frac_reg:0x%x) in table, we will caculate it\n",
> > +			reg_int, reg_frac);

<snip>

> > +	div_table->rate = (ulong)(parent_rate * n) / ((ulong)reg_int * n + m);
> 
> Please don't use ulong, use unsigned long. Also consider using
> local variables so the line isn't overly long.

It seems to me that none of the type casts is really necessary.  So I
will just drop them.

> 
> > +	div_table->int_reg = reg_int;
> > +	div_table->frac_reg = reg_frac;
> > +}
> [...]
> > +
> > +static int zx_audio_div_set_rate(struct clk_hw *hw, unsigned long rate,
> > +				    unsigned long parent_rate)
> > +{
> > +	struct clk_zx_audio_divider *zx_audio_div = to_clk_zx_audio_div(hw);
> > +	struct zx_clk_audio_div_table divt;
> > +	unsigned int val;
> > +
> > +	audio_calc_reg(zx_audio_div, &divt, rate, parent_rate);
> > +	if (divt.rate != rate)
> > +		pr_info("the real rate is:%ld", divt.rate);
> 
> Debug noise?

I will change it to pr_debug.

Shawn
diff mbox

Patch

diff --git a/drivers/clk/zte/clk-zx296718.c b/drivers/clk/zte/clk-zx296718.c
index 707d62956e9b..eed8581b1b25 100644
--- a/drivers/clk/zte/clk-zx296718.c
+++ b/drivers/clk/zte/clk-zx296718.c
@@ -888,10 +888,160 @@  static int __init lsp1_clocks_init(struct device_node *np)
 	return 0;
 }
 
+PNAME(audio_wclk_common_p) = {
+	"audio_99m",
+	"audio_24m",
+};
+
+PNAME(audio_timer_p) = {
+	"audio_24m",
+	"audio_32k",
+};
+
+static struct zx_clk_mux audio_mux_clk[] = {
+	MUX(0, "i2s0_wclk_mux", audio_wclk_common_p, AUDIO_I2S0_CLK, 0, 1),
+	MUX(0, "i2s1_wclk_mux", audio_wclk_common_p, AUDIO_I2S1_CLK, 0, 1),
+	MUX(0, "i2s2_wclk_mux", audio_wclk_common_p, AUDIO_I2S2_CLK, 0, 1),
+	MUX(0, "i2s3_wclk_mux", audio_wclk_common_p, AUDIO_I2S3_CLK, 0, 1),
+	MUX(0, "i2c0_wclk_mux", audio_wclk_common_p, AUDIO_I2C0_CLK, 0, 1),
+	MUX(0, "spdif0_wclk_mux", audio_wclk_common_p, AUDIO_SPDIF0_CLK, 0, 1),
+	MUX(0, "spdif1_wclk_mux", audio_wclk_common_p, AUDIO_SPDIF1_CLK, 0, 1),
+	MUX(0, "timer_wclk_mux", audio_timer_p, AUDIO_TIMER_CLK, 0, 1),
+};
+
+struct zx_clk_audio_div_table i2s_wclk_div_table[] = {
+	{2048000, 0x3000030, 0xffff5700},
+	{4096000, 0x3000018, 0xffff2b80},
+	{2822400, 0x3000011, 0xffff89cb},
+	{3072000, 0x3000010, 0xffff1d00},
+	{4096000, 0x300000c, 0xffff15c0},
+	{5644800, 0x3000008, 0xffffc4e5},
+	{6144000, 0x3000008, 0xffff0e80},
+	{11289600, 0x3000004, 0xffff6273},
+	{12288000, 0x3000004, 0xffff0740},
+	{22579200, 0x3000002, 0xffff3139},
+	{24576000, 0x3000002, 0xffff03a0},
+};
+
+struct zx_clk_audio_div_table spdif_wclk_div_table[] = {
+	{2822400, 0x00023, 0xffff1397},
+	{3072000, 0x00020, 0xffff3a00},
+	{4096000, 0x00018, 0xffff2b80},
+	{5644800, 0x00011, 0xffff89cb},
+	{6144000, 0x00010, 0xffff1d00},
+	{11289600, 0x00008, 0xffffc4e5},
+	{12288000, 0x00008, 0xffff0e80},
+	{22579200, 0x00004, 0xffff6273},
+	{24576000, 0x00004, 0xffff0740},
+};
+
+struct clk_zx_audio_divider audio_adiv_clk[] = {
+	AUDIO_DIV(0, "i2s0_wclk_div", "i2s0_wclk_mux", AUDIO_I2S0_DIV_CFG1, i2s_wclk_div_table),
+	AUDIO_DIV(0, "i2s1_wclk_div", "i2s1_wclk_mux", AUDIO_I2S1_DIV_CFG1, i2s_wclk_div_table),
+	AUDIO_DIV(0, "i2s2_wclk_div", "i2s2_wclk_mux", AUDIO_I2S2_DIV_CFG1, i2s_wclk_div_table),
+	AUDIO_DIV(0, "i2s3_wclk_div", "i2s3_wclk_mux", AUDIO_I2S3_DIV_CFG1, i2s_wclk_div_table),
+	AUDIO_DIV(0, "spdif0_wclk_div", "spdif0_wclk_mux", AUDIO_SPDIF0_DIV_CFG1, spdif_wclk_div_table),
+	AUDIO_DIV(0, "spdif1_wclk_div", "spdif1_wclk_mux", AUDIO_SPDIF1_DIV_CFG1, spdif_wclk_div_table),
+};
+
+struct zx_clk_div audio_div_clk[] = {
+	DIV_T(0, "tdm_wclk_div", "audio_16m384", AUDIO_TDM_CLK, 8, 4, 0, common_div_table),
+};
+
+struct zx_clk_gate audio_gate_clk[] = {
+	GATE(AUDIO_I2S0_WCLK, "i2s0_wclk", "i2s0_wclk_div", AUDIO_I2S0_CLK, 9, CLK_SET_RATE_PARENT, 0),
+	GATE(AUDIO_I2S1_WCLK, "i2s1_wclk", "i2s1_wclk_div", AUDIO_I2S1_CLK, 9, CLK_SET_RATE_PARENT, 0),
+	GATE(AUDIO_I2S2_WCLK, "i2s2_wclk", "i2s2_wclk_div", AUDIO_I2S2_CLK, 9, CLK_SET_RATE_PARENT, 0),
+	GATE(AUDIO_I2S3_WCLK, "i2s3_wclk", "i2s3_wclk_div", AUDIO_I2S3_CLK, 9, CLK_SET_RATE_PARENT, 0),
+	GATE(AUDIO_I2C0_WCLK, "i2c0_wclk", "i2c0_wclk_mux", AUDIO_I2C0_CLK, 9, CLK_SET_RATE_PARENT, 0),
+	GATE(AUDIO_SPDIF0_WCLK, "spdif0_wclk", "spdif0_wclk_div", AUDIO_SPDIF0_CLK, 9, CLK_SET_RATE_PARENT, 0),
+	GATE(AUDIO_SPDIF1_WCLK, "spdif1_wclk", "spdif1_wclk_div", AUDIO_SPDIF1_CLK, 9, CLK_SET_RATE_PARENT, 0),
+	GATE(AUDIO_TDM_WCLK, "tdm_wclk", "tdm_wclk_div", AUDIO_TDM_CLK, 17, CLK_SET_RATE_PARENT, 0),
+	GATE(AUDIO_TS_PCLK, "tempsensor_pclk", "clk49m5", AUDIO_TS_CLK, 1, 0, 0),
+};
+
+static struct clk_hw_onecell_data audio_hw_onecell_data = {
+	.num = AUDIO_NR_CLKS,
+	.hws = {
+		[AUDIO_NR_CLKS - 1] = NULL,
+	},
+};
+
+static int __init audio_clocks_init(struct device_node *np)
+{
+	void __iomem *reg_base;
+	int i, ret;
+
+	reg_base = of_iomap(np, 0);
+	if (!reg_base) {
+		pr_err("%s: Unable to map audio clk base\n", __func__);
+		return -ENXIO;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(audio_mux_clk); i++) {
+		if (audio_mux_clk[i].id)
+			audio_hw_onecell_data.hws[audio_mux_clk[i].id] =
+					&audio_mux_clk[i].mux.hw;
+
+		audio_mux_clk[i].mux.reg += (u64)reg_base;
+		ret = clk_hw_register(NULL, &audio_mux_clk[i].mux.hw);
+		if (ret) {
+			pr_warn("audio clk %s init error!\n",
+				audio_mux_clk[i].mux.hw.init->name);
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(audio_adiv_clk); i++) {
+		if (audio_adiv_clk[i].id)
+			audio_hw_onecell_data.hws[audio_adiv_clk[i].id] =
+					&audio_adiv_clk[i].hw;
+
+		audio_adiv_clk[i].reg_base += (u64)reg_base;
+		ret = clk_hw_register(NULL, &audio_adiv_clk[i].hw);
+		if (ret) {
+			pr_warn("audio clk %s init error!\n",
+				audio_adiv_clk[i].hw.init->name);
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(audio_div_clk); i++) {
+		if (audio_div_clk[i].id)
+			audio_hw_onecell_data.hws[audio_div_clk[i].id] =
+					&audio_div_clk[i].div.hw;
+
+		audio_div_clk[i].div.reg += (u64)reg_base;
+		ret = clk_hw_register(NULL, &audio_div_clk[i].div.hw);
+		if (ret) {
+			pr_warn("audio clk %s init error!\n",
+				audio_div_clk[i].div.hw.init->name);
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(audio_gate_clk); i++) {
+		if (audio_gate_clk[i].id)
+			audio_hw_onecell_data.hws[audio_gate_clk[i].id] =
+					&audio_gate_clk[i].gate.hw;
+
+		audio_gate_clk[i].gate.reg += (u64)reg_base;
+		ret = clk_hw_register(NULL, &audio_gate_clk[i].gate.hw);
+		if (ret) {
+			pr_warn("audio clk %s init error!\n",
+				audio_gate_clk[i].gate.hw.init->name);
+		}
+	}
+
+	if (of_clk_add_hw_provider(np, of_clk_hw_onecell_get, &audio_hw_onecell_data))
+		panic("could not register clk provider\n");
+	pr_info("audio-clk init over, nr:%d\n", AUDIO_NR_CLKS);
+
+	return 0;
+}
+
 static const struct of_device_id zx_clkc_match_table[] = {
 	{ .compatible = "zte,zx296718-topcrm", .data = &top_clocks_init },
 	{ .compatible = "zte,zx296718-lsp0crm", .data = &lsp0_clocks_init },
 	{ .compatible = "zte,zx296718-lsp1crm", .data = &lsp1_clocks_init },
+	{ .compatible = "zte,zx296718-audiocrm", .data = &audio_clocks_init },
 	{ }
 };
 
diff --git a/drivers/clk/zte/clk.c b/drivers/clk/zte/clk.c
index c4c1251bc1e7..ea97024b37aa 100644
--- a/drivers/clk/zte/clk.c
+++ b/drivers/clk/zte/clk.c
@@ -9,6 +9,7 @@ 
 
 #include <linux/clk-provider.h>
 #include <linux/err.h>
+#include <linux/gcd.h>
 #include <linux/io.h>
 #include <linux/iopoll.h>
 #include <linux/slab.h>
@@ -310,3 +311,151 @@  struct clk *clk_register_zx_audio(const char *name,
 
 	return clk;
 }
+
+#define CLK_AUDIO_DIV_FRAC	BIT(0)
+#define CLK_AUDIO_DIV_INT	BIT(1)
+#define CLK_AUDIO_DIV_UNCOMMON	BIT(1)
+
+#define CLK_AUDIO_DIV_FRAC_NSHIFT	16
+#define CLK_AUDIO_DIV_INT_FRAC_RE	BIT(16)
+#define CLK_AUDIO_DIV_INT_FRAC_MAX	(0xffff)
+#define CLK_AUDIO_DIV_INT_FRAC_MIN	(0x2)
+#define CLK_AUDIO_DIV_INT_INT_SHIFT	24
+#define CLK_AUDIO_DIV_INT_INT_WIDTH	4
+
+#define to_clk_zx_audio_div(_hw) container_of(_hw, struct clk_zx_audio_divider, hw)
+
+static unsigned long audio_calc_rate(struct clk_zx_audio_divider *audio_div,
+				     u32 reg_frac, u32 reg_int,
+				     unsigned long parent_rate)
+{
+	unsigned long rate, m, n;
+
+	if (audio_div->table) {
+		const struct zx_clk_audio_div_table *divt = audio_div->table;
+
+		for (; divt->rate; divt++) {
+			if ((divt->int_reg == reg_int) && (divt->frac_reg == reg_frac))
+				return divt->rate;
+		}
+	}
+	if (audio_div->table)
+		pr_warn("cannot found the config(int_reg:0x%x, frac_reg:0x%x) in table, we will caculate it\n",
+			reg_int, reg_frac);
+
+	m = reg_frac & 0xffff;
+	n = (reg_frac >> 16) & 0xffff;
+
+	m = (reg_int & 0xffff) * n + m;
+	rate = (parent_rate * n) / m;
+
+	return rate;
+}
+
+static void audio_calc_reg(struct clk_zx_audio_divider *audio_div,
+			   struct zx_clk_audio_div_table *div_table,
+			   unsigned long rate, unsigned long parent_rate)
+{
+	unsigned int reg_int, reg_frac;
+	unsigned long m, n, div;
+
+	if (audio_div->table) {
+		const struct zx_clk_audio_div_table *divt = audio_div->table;
+
+		for (; divt->rate; divt++) {
+			if (divt->rate == rate) {
+				div_table->rate = divt->rate;
+				div_table->int_reg = divt->int_reg;
+				div_table->frac_reg = divt->frac_reg;
+				return;
+			}
+		}
+	}
+	if (audio_div->table)
+		pr_warn("cannot found the rate(%ld) in table, we will caculate the config\n",
+			rate);
+
+	reg_int = parent_rate / rate;
+
+	if (reg_int > CLK_AUDIO_DIV_INT_FRAC_MAX)
+		reg_int = CLK_AUDIO_DIV_INT_FRAC_MAX;
+	else if (reg_int < CLK_AUDIO_DIV_INT_FRAC_MIN)
+		reg_int = 0;
+	m = parent_rate - rate * reg_int;
+	n = rate;
+
+	div = gcd(m, n);
+	m = m / div;
+	n = n / div;
+
+	if ((m >> 16) || (n >> 16)) {
+		if (m > n) {
+			n = n * 0xffff / m;
+			m = 0xffff;
+		} else {
+			m = m * 0xffff / n;
+			n = 0xffff;
+		}
+	}
+	reg_frac = m | (n << 16);
+
+	div_table->rate = (ulong)(parent_rate * n) / ((ulong)reg_int * n + m);
+	div_table->int_reg = reg_int;
+	div_table->frac_reg = reg_frac;
+}
+
+static unsigned long zx_audio_div_recalc_rate(struct clk_hw *hw,
+					  unsigned long parent_rate)
+{
+	struct clk_zx_audio_divider *zx_audio_div = to_clk_zx_audio_div(hw);
+	u32 reg_frac, reg_int;
+
+	reg_frac = readl_relaxed(zx_audio_div->reg_base);
+	reg_int = readl_relaxed(zx_audio_div->reg_base + 0x4);
+
+	return audio_calc_rate(zx_audio_div, reg_frac, reg_int, parent_rate);
+}
+
+static long zx_audio_div_round_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long *prate)
+{
+	struct clk_zx_audio_divider *zx_audio_div = to_clk_zx_audio_div(hw);
+	struct zx_clk_audio_div_table divt;
+
+	audio_calc_reg(zx_audio_div, &divt, rate, *prate);
+
+	return audio_calc_rate(zx_audio_div, divt.frac_reg, divt.int_reg, *prate);
+}
+
+static int zx_audio_div_set_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long parent_rate)
+{
+	struct clk_zx_audio_divider *zx_audio_div = to_clk_zx_audio_div(hw);
+	struct zx_clk_audio_div_table divt;
+	unsigned int val;
+
+	audio_calc_reg(zx_audio_div, &divt, rate, parent_rate);
+	if (divt.rate != rate)
+		pr_info("the real rate is:%ld", divt.rate);
+
+	writel_relaxed(divt.frac_reg, zx_audio_div->reg_base);
+
+	val = readl_relaxed(zx_audio_div->reg_base + 0x4);
+	val &= ~0xffff;
+	val |= divt.int_reg | CLK_AUDIO_DIV_INT_FRAC_RE;
+	writel_relaxed(val, zx_audio_div->reg_base + 0x4);
+
+	mdelay(1);
+
+	val = readl_relaxed(zx_audio_div->reg_base + 0x4);
+	val &= ~CLK_AUDIO_DIV_INT_FRAC_RE;
+	writel_relaxed(val, zx_audio_div->reg_base + 0x4);
+
+	return 0;
+}
+
+const struct clk_ops zx_audio_div_ops = {
+	.recalc_rate = zx_audio_div_recalc_rate,
+	.round_rate = zx_audio_div_round_rate,
+	.set_rate = zx_audio_div_set_rate,
+};
diff --git a/drivers/clk/zte/clk.h b/drivers/clk/zte/clk.h
index 0df3474b2cf3..6e7ccb752c24 100644
--- a/drivers/clk/zte/clk.h
+++ b/drivers/clk/zte/clk.h
@@ -153,6 +153,32 @@  struct zx_clk_div {
 	.id = _id,							\
 }
 
+struct zx_clk_audio_div_table {
+	unsigned long rate;
+	unsigned int int_reg;
+	unsigned int frac_reg;
+};
+
+struct clk_zx_audio_divider {
+	struct clk_hw				hw;
+	void __iomem				*reg_base;
+	const struct zx_clk_audio_div_table	*table;
+	unsigned int				rate_count;
+	spinlock_t				*lock;
+	u16					id;
+};
+
+#define AUDIO_DIV(_id, _name, _parent, _reg, _table)			\
+{									\
+	.reg_base	= (void __iomem *) _reg,			\
+	.lock		= &clk_lock,					\
+	.hw.init	= CLK_HW_INIT(_name,				\
+				      _parent,				\
+				      &zx_audio_div_ops,		\
+				      0),				\
+	.id = _id,							\
+}
+
 struct clk *clk_register_zx_pll(const char *name, const char *parent_name,
 	unsigned long flags, void __iomem *reg_base,
 	const struct zx_pll_config *lookup_table, int count, spinlock_t *lock);
@@ -167,4 +193,6 @@  struct clk *clk_register_zx_audio(const char *name,
 				  unsigned long flags, void __iomem *reg_base);
 
 extern const struct clk_ops zx_pll_ops;
+extern const struct clk_ops zx_audio_div_ops;
+
 #endif