diff mbox

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

Message ID 1481189157-8995-2-git-send-email-shawnguo@kernel.org
State New
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(+)

-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

>

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
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,

> +};

> 


-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
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
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
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