[RESEND PATCH 2/2] ASoC: stm32: i2s: add master clock provider

2021-02-05 Thread Olivier Moysan
From: Olivier Moysan 

Add master clock generation support in STM32 I2S driver.
The master clock provided by I2S can be used to feed a codec.

Signed-off-by: Olivier Moysan 
---
 sound/soc/stm/stm32_i2s.c | 310 --
 1 file changed, 266 insertions(+), 44 deletions(-)

diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c
index 7c4d63c33f15..7d1672cf78cc 100644
--- a/sound/soc/stm/stm32_i2s.c
+++ b/sound/soc/stm/stm32_i2s.c
@@ -8,6 +8,7 @@
 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -196,6 +197,9 @@ enum i2s_datlen {
 #define STM32_I2S_IS_MASTER(x) ((x)->ms_flg == I2S_MS_MASTER)
 #define STM32_I2S_IS_SLAVE(x)  ((x)->ms_flg == I2S_MS_SLAVE)
 
+#define STM32_I2S_NAME_LEN 32
+#define STM32_I2S_RATE_11K 11025
+
 /**
  * struct stm32_i2s_data - private data of I2S
  * @regmap_conf: I2S register map configuration pointer
@@ -206,6 +210,7 @@ enum i2s_datlen {
  * @dma_data_rx: dma configuration data for tx channel
  * @substream: PCM substream data pointer
  * @i2sclk: kernel clock feeding the I2S clock generator
+ * @i2smclk: master clock from I2S mclk provider
  * @pclk: peripheral clock driving bus interface
  * @x8kclk: I2S parent clock for sampling frequencies multiple of 8kHz
  * @x11kclk: I2S parent clock for sampling frequencies multiple of 11kHz
@@ -215,6 +220,9 @@ enum i2s_datlen {
  * @irq_lock: prevent race condition with IRQ
  * @mclk_rate: master clock frequency (Hz)
  * @fmt: DAI protocol
+ * @divider: prescaler division ratio
+ * @div: prescaler div field
+ * @odd: prescaler odd field
  * @refcount: keep count of opened streams on I2S
  * @ms_flg: master mode flag.
  */
@@ -227,6 +235,7 @@ struct stm32_i2s_data {
struct snd_dmaengine_dai_dma_data dma_data_rx;
struct snd_pcm_substream *substream;
struct clk *i2sclk;
+   struct clk *i2smclk;
struct clk *pclk;
struct clk *x8kclk;
struct clk *x11kclk;
@@ -236,10 +245,210 @@ struct stm32_i2s_data {
spinlock_t irq_lock; /* used to prevent race condition with IRQ */
unsigned int mclk_rate;
unsigned int fmt;
+   unsigned int divider;
+   unsigned int div;
+   bool odd;
int refcount;
int ms_flg;
 };
 
+struct stm32_i2smclk_data {
+   struct clk_hw hw;
+   unsigned long freq;
+   struct stm32_i2s_data *i2s_data;
+};
+
+#define to_mclk_data(_hw) container_of(_hw, struct stm32_i2smclk_data, hw)
+
+static int stm32_i2s_calc_clk_div(struct stm32_i2s_data *i2s,
+ unsigned long input_rate,
+ unsigned long output_rate)
+{
+   unsigned int ratio, div, divider = 1;
+   bool odd;
+
+   ratio = DIV_ROUND_CLOSEST(input_rate, output_rate);
+
+   /* Check the parity of the divider */
+   odd = ratio & 0x1;
+
+   /* Compute the div prescaler */
+   div = ratio >> 1;
+
+   /* If div is 0 actual divider is 1 */
+   if (div) {
+   divider = ((2 * div) + odd);
+   dev_dbg(>pdev->dev, "Divider: 2*%d(div)+%d(odd) = %d\n",
+   div, odd, divider);
+   }
+
+   /* Division by three is not allowed by I2S prescaler */
+   if ((div == 1 && odd) || div > I2S_CGFR_I2SDIV_MAX) {
+   dev_err(>pdev->dev, "Wrong divider setting\n");
+   return -EINVAL;
+   }
+
+   if (input_rate % divider)
+   dev_dbg(>pdev->dev,
+   "Rate not accurate. requested (%ld), actual (%ld)\n",
+   output_rate, input_rate / divider);
+
+   i2s->div = div;
+   i2s->odd = odd;
+   i2s->divider = divider;
+
+   return 0;
+}
+
+static int stm32_i2s_set_clk_div(struct stm32_i2s_data *i2s)
+{
+   u32 cgfr, cgfr_mask;
+
+   cgfr = I2S_CGFR_I2SDIV_SET(i2s->div) | (i2s->odd << I2S_CGFR_ODD_SHIFT);
+   cgfr_mask = I2S_CGFR_I2SDIV_MASK | I2S_CGFR_ODD;
+
+   return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
+ cgfr_mask, cgfr);
+}
+
+static int stm32_i2s_set_parent_clock(struct stm32_i2s_data *i2s,
+ unsigned int rate)
+{
+   struct platform_device *pdev = i2s->pdev;
+   struct clk *parent_clk;
+   int ret;
+
+   if (!(rate % STM32_I2S_RATE_11K))
+   parent_clk = i2s->x11kclk;
+   else
+   parent_clk = i2s->x8kclk;
+
+   ret = clk_set_parent(i2s->i2sclk, parent_clk);
+   if (ret)
+   dev_err(>dev,
+   "Error %d setting i2sclk parent clock\n", ret);
+
+   return ret;
+}
+
+static long stm32_i2smclk_round_rate(struct clk_hw *hw, unsigned long rate,
+unsigned long *prate)
+{
+   struct stm32_i2smclk_data *mclk = to_mclk_data(hw);
+   struct stm32_i2s_data *i2s = mclk->i2s_data;
+   int ret;
+
+   ret = 

[PATCH 2/2] ASoC: stm32: i2s: add master clock provider

2020-09-11 Thread Olivier Moysan
Add master clock generation support in STM32 I2S driver.
The master clock provided by I2S can be used to feed a codec.

Signed-off-by: Olivier Moysan 
---
 sound/soc/stm/stm32_i2s.c | 310 --
 1 file changed, 266 insertions(+), 44 deletions(-)

diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c
index 7c4d63c33f15..7d1672cf78cc 100644
--- a/sound/soc/stm/stm32_i2s.c
+++ b/sound/soc/stm/stm32_i2s.c
@@ -8,6 +8,7 @@
 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -196,6 +197,9 @@ enum i2s_datlen {
 #define STM32_I2S_IS_MASTER(x) ((x)->ms_flg == I2S_MS_MASTER)
 #define STM32_I2S_IS_SLAVE(x)  ((x)->ms_flg == I2S_MS_SLAVE)
 
+#define STM32_I2S_NAME_LEN 32
+#define STM32_I2S_RATE_11K 11025
+
 /**
  * struct stm32_i2s_data - private data of I2S
  * @regmap_conf: I2S register map configuration pointer
@@ -206,6 +210,7 @@ enum i2s_datlen {
  * @dma_data_rx: dma configuration data for tx channel
  * @substream: PCM substream data pointer
  * @i2sclk: kernel clock feeding the I2S clock generator
+ * @i2smclk: master clock from I2S mclk provider
  * @pclk: peripheral clock driving bus interface
  * @x8kclk: I2S parent clock for sampling frequencies multiple of 8kHz
  * @x11kclk: I2S parent clock for sampling frequencies multiple of 11kHz
@@ -215,6 +220,9 @@ enum i2s_datlen {
  * @irq_lock: prevent race condition with IRQ
  * @mclk_rate: master clock frequency (Hz)
  * @fmt: DAI protocol
+ * @divider: prescaler division ratio
+ * @div: prescaler div field
+ * @odd: prescaler odd field
  * @refcount: keep count of opened streams on I2S
  * @ms_flg: master mode flag.
  */
@@ -227,6 +235,7 @@ struct stm32_i2s_data {
struct snd_dmaengine_dai_dma_data dma_data_rx;
struct snd_pcm_substream *substream;
struct clk *i2sclk;
+   struct clk *i2smclk;
struct clk *pclk;
struct clk *x8kclk;
struct clk *x11kclk;
@@ -236,10 +245,210 @@ struct stm32_i2s_data {
spinlock_t irq_lock; /* used to prevent race condition with IRQ */
unsigned int mclk_rate;
unsigned int fmt;
+   unsigned int divider;
+   unsigned int div;
+   bool odd;
int refcount;
int ms_flg;
 };
 
+struct stm32_i2smclk_data {
+   struct clk_hw hw;
+   unsigned long freq;
+   struct stm32_i2s_data *i2s_data;
+};
+
+#define to_mclk_data(_hw) container_of(_hw, struct stm32_i2smclk_data, hw)
+
+static int stm32_i2s_calc_clk_div(struct stm32_i2s_data *i2s,
+ unsigned long input_rate,
+ unsigned long output_rate)
+{
+   unsigned int ratio, div, divider = 1;
+   bool odd;
+
+   ratio = DIV_ROUND_CLOSEST(input_rate, output_rate);
+
+   /* Check the parity of the divider */
+   odd = ratio & 0x1;
+
+   /* Compute the div prescaler */
+   div = ratio >> 1;
+
+   /* If div is 0 actual divider is 1 */
+   if (div) {
+   divider = ((2 * div) + odd);
+   dev_dbg(>pdev->dev, "Divider: 2*%d(div)+%d(odd) = %d\n",
+   div, odd, divider);
+   }
+
+   /* Division by three is not allowed by I2S prescaler */
+   if ((div == 1 && odd) || div > I2S_CGFR_I2SDIV_MAX) {
+   dev_err(>pdev->dev, "Wrong divider setting\n");
+   return -EINVAL;
+   }
+
+   if (input_rate % divider)
+   dev_dbg(>pdev->dev,
+   "Rate not accurate. requested (%ld), actual (%ld)\n",
+   output_rate, input_rate / divider);
+
+   i2s->div = div;
+   i2s->odd = odd;
+   i2s->divider = divider;
+
+   return 0;
+}
+
+static int stm32_i2s_set_clk_div(struct stm32_i2s_data *i2s)
+{
+   u32 cgfr, cgfr_mask;
+
+   cgfr = I2S_CGFR_I2SDIV_SET(i2s->div) | (i2s->odd << I2S_CGFR_ODD_SHIFT);
+   cgfr_mask = I2S_CGFR_I2SDIV_MASK | I2S_CGFR_ODD;
+
+   return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
+ cgfr_mask, cgfr);
+}
+
+static int stm32_i2s_set_parent_clock(struct stm32_i2s_data *i2s,
+ unsigned int rate)
+{
+   struct platform_device *pdev = i2s->pdev;
+   struct clk *parent_clk;
+   int ret;
+
+   if (!(rate % STM32_I2S_RATE_11K))
+   parent_clk = i2s->x11kclk;
+   else
+   parent_clk = i2s->x8kclk;
+
+   ret = clk_set_parent(i2s->i2sclk, parent_clk);
+   if (ret)
+   dev_err(>dev,
+   "Error %d setting i2sclk parent clock\n", ret);
+
+   return ret;
+}
+
+static long stm32_i2smclk_round_rate(struct clk_hw *hw, unsigned long rate,
+unsigned long *prate)
+{
+   struct stm32_i2smclk_data *mclk = to_mclk_data(hw);
+   struct stm32_i2s_data *i2s = mclk->i2s_data;
+   int ret;
+
+   ret = stm32_i2s_calc_clk_div(i2s, *prate, rate);
+