mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-16 09:56:46 +00:00
ASoC: stm32: i2s: add stm32mp25 support
Merge series from Olivier Moysan <olivier.moysan@foss.st.com>: Update STM32 I2S driver and binding to support STM32MP25 SoCs.
This commit is contained in:
commit
125d0f698a
@ -13,13 +13,11 @@ description:
|
||||
The SPI/I2S block supports I2S/PCM protocols when configured on I2S mode.
|
||||
Only some SPI instances support I2S.
|
||||
|
||||
allOf:
|
||||
- $ref: dai-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- st,stm32h7-i2s
|
||||
- st,stm32mp25-i2s
|
||||
|
||||
"#sound-dai-cells":
|
||||
const: 0
|
||||
@ -33,6 +31,7 @@ properties:
|
||||
- description: clock feeding the internal clock generator.
|
||||
- description: I2S parent clock for sampling rates multiple of 8kHz.
|
||||
- description: I2S parent clock for sampling rates multiple of 11.025kHz.
|
||||
minItems: 2
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
@ -40,6 +39,7 @@ properties:
|
||||
- const: i2sclk
|
||||
- const: x8k
|
||||
- const: x11k
|
||||
minItems: 2
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
@ -79,6 +79,36 @@ required:
|
||||
- dmas
|
||||
- dma-names
|
||||
|
||||
allOf:
|
||||
- $ref: dai-common.yaml#
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: st,stm32h7-i2s
|
||||
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 4
|
||||
|
||||
clock-names:
|
||||
minItems: 4
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: st,stm32mp25-i2s
|
||||
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
maxItems: 2
|
||||
|
||||
clock-names:
|
||||
maxItems: 2
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
|
@ -200,10 +200,13 @@ enum i2s_datlen {
|
||||
|
||||
#define STM32_I2S_NAME_LEN 32
|
||||
#define STM32_I2S_RATE_11K 11025
|
||||
#define STM32_I2S_MAX_SAMPLE_RATE_8K 192000
|
||||
#define STM32_I2S_MAX_SAMPLE_RATE_11K 176400
|
||||
#define STM32_I2S_CLK_RATE_TOLERANCE 1000 /* ppm */
|
||||
|
||||
/**
|
||||
* struct stm32_i2s_data - private data of I2S
|
||||
* @regmap_conf: I2S register map configuration pointer
|
||||
* @conf: I2S configuration pointer
|
||||
* @regmap: I2S register map pointer
|
||||
* @pdev: device data pointer
|
||||
* @dai_drv: DAI driver pointer
|
||||
@ -224,11 +227,14 @@ enum i2s_datlen {
|
||||
* @divider: prescaler division ratio
|
||||
* @div: prescaler div field
|
||||
* @odd: prescaler odd field
|
||||
* @i2s_clk_flg: flag set while exclusivity on I2S kernel clock is active
|
||||
* @refcount: keep count of opened streams on I2S
|
||||
* @ms_flg: master mode flag.
|
||||
* @set_i2s_clk_rate: set I2S kernel clock rate
|
||||
* @put_i2s_clk_rate: put I2S kernel clock rate
|
||||
*/
|
||||
struct stm32_i2s_data {
|
||||
const struct regmap_config *regmap_conf;
|
||||
const struct stm32_i2s_conf *conf;
|
||||
struct regmap *regmap;
|
||||
struct platform_device *pdev;
|
||||
struct snd_soc_dai_driver *dai_drv;
|
||||
@ -249,8 +255,21 @@ struct stm32_i2s_data {
|
||||
unsigned int divider;
|
||||
unsigned int div;
|
||||
bool odd;
|
||||
bool i2s_clk_flg;
|
||||
int refcount;
|
||||
int ms_flg;
|
||||
int (*set_i2s_clk_rate)(struct stm32_i2s_data *i2s, unsigned int rate);
|
||||
void (*put_i2s_clk_rate)(struct stm32_i2s_data *i2s);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct stm32_i2s_conf - I2S configuration
|
||||
* @regmap_conf: regmap configuration pointer
|
||||
* @get_i2s_clk_parent: get parent clock of I2S kernel clock
|
||||
*/
|
||||
struct stm32_i2s_conf {
|
||||
const struct regmap_config *regmap_conf;
|
||||
int (*get_i2s_clk_parent)(struct stm32_i2s_data *i2s);
|
||||
};
|
||||
|
||||
struct stm32_i2smclk_data {
|
||||
@ -261,6 +280,8 @@ struct stm32_i2smclk_data {
|
||||
|
||||
#define to_mclk_data(_hw) container_of(_hw, struct stm32_i2smclk_data, hw)
|
||||
|
||||
static int stm32_i2s_get_parent_clk(struct stm32_i2s_data *i2s);
|
||||
|
||||
static int stm32_i2s_calc_clk_div(struct stm32_i2s_data *i2s,
|
||||
unsigned long input_rate,
|
||||
unsigned long output_rate)
|
||||
@ -312,6 +333,33 @@ static int stm32_i2s_set_clk_div(struct stm32_i2s_data *i2s)
|
||||
cgfr_mask, cgfr);
|
||||
}
|
||||
|
||||
static bool stm32_i2s_rate_accurate(struct stm32_i2s_data *i2s,
|
||||
unsigned int max_rate, unsigned int rate)
|
||||
{
|
||||
struct platform_device *pdev = i2s->pdev;
|
||||
u64 delta, dividend;
|
||||
int ratio;
|
||||
|
||||
if (!rate) {
|
||||
dev_err(&pdev->dev, "Unexpected null rate\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ratio = DIV_ROUND_CLOSEST(max_rate, rate);
|
||||
if (!ratio)
|
||||
return false;
|
||||
|
||||
dividend = mul_u32_u32(1000000, abs(max_rate - (ratio * rate)));
|
||||
delta = div_u64(dividend, max_rate);
|
||||
|
||||
if (delta <= STM32_I2S_CLK_RATE_TOLERANCE)
|
||||
return true;
|
||||
|
||||
dev_dbg(&pdev->dev, "Rate [%u] not accurate\n", rate);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int stm32_i2s_set_parent_clock(struct stm32_i2s_data *i2s,
|
||||
unsigned int rate)
|
||||
{
|
||||
@ -332,6 +380,87 @@ static int stm32_i2s_set_parent_clock(struct stm32_i2s_data *i2s,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void stm32_i2s_put_parent_rate(struct stm32_i2s_data *i2s)
|
||||
{
|
||||
if (i2s->i2s_clk_flg) {
|
||||
i2s->i2s_clk_flg = false;
|
||||
clk_rate_exclusive_put(i2s->i2sclk);
|
||||
}
|
||||
}
|
||||
|
||||
static int stm32_i2s_set_parent_rate(struct stm32_i2s_data *i2s,
|
||||
unsigned int rate)
|
||||
{
|
||||
struct platform_device *pdev = i2s->pdev;
|
||||
unsigned int i2s_clk_rate, i2s_clk_max_rate, i2s_curr_rate, i2s_new_rate;
|
||||
int ret, div;
|
||||
|
||||
/*
|
||||
* Set maximum expected kernel clock frequency
|
||||
* - mclk on:
|
||||
* f_i2s_ck = MCKDIV * mclk-fs * fs
|
||||
* Here typical 256 ratio is assumed for mclk-fs
|
||||
* - mclk off:
|
||||
* f_i2s_ck = MCKDIV * FRL * fs
|
||||
* Where FRL=[16,32], MCKDIV=[1..256]
|
||||
* f_i2s_ck = i2s_clk_max_rate * 32 / 256
|
||||
*/
|
||||
if (!(rate % STM32_I2S_RATE_11K))
|
||||
i2s_clk_max_rate = STM32_I2S_MAX_SAMPLE_RATE_11K * 256;
|
||||
else
|
||||
i2s_clk_max_rate = STM32_I2S_MAX_SAMPLE_RATE_8K * 256;
|
||||
|
||||
if (!i2s->i2smclk)
|
||||
i2s_clk_max_rate /= 8;
|
||||
|
||||
/* Request exclusivity, as the clock may be shared by I2S instances */
|
||||
clk_rate_exclusive_get(i2s->i2sclk);
|
||||
i2s->i2s_clk_flg = true;
|
||||
|
||||
/*
|
||||
* Check current kernel clock rate. If it gives the expected accuracy
|
||||
* return immediately.
|
||||
*/
|
||||
i2s_curr_rate = clk_get_rate(i2s->i2sclk);
|
||||
if (stm32_i2s_rate_accurate(i2s, i2s_clk_max_rate, i2s_curr_rate))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Otherwise try to set the maximum rate and check the new actual rate.
|
||||
* If the new rate does not give the expected accuracy, try to set
|
||||
* lower rates for the kernel clock.
|
||||
*/
|
||||
i2s_clk_rate = i2s_clk_max_rate;
|
||||
div = 1;
|
||||
do {
|
||||
/* Check new rate accuracy. Return if ok */
|
||||
i2s_new_rate = clk_round_rate(i2s->i2sclk, i2s_clk_rate);
|
||||
if (stm32_i2s_rate_accurate(i2s, i2s_clk_rate, i2s_new_rate)) {
|
||||
ret = clk_set_rate(i2s->i2sclk, i2s_clk_rate);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Error %d setting i2s_clk_rate rate. %s",
|
||||
ret, ret == -EBUSY ?
|
||||
"Active stream rates may be in conflict\n" : "\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Try a lower frequency */
|
||||
div++;
|
||||
i2s_clk_rate = i2s_clk_max_rate / div;
|
||||
} while (i2s_clk_rate > rate);
|
||||
|
||||
/* no accurate rate found */
|
||||
dev_err(&pdev->dev, "Failed to find an accurate rate");
|
||||
|
||||
err:
|
||||
stm32_i2s_put_parent_rate(i2s);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static long stm32_i2smclk_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long *prate)
|
||||
{
|
||||
@ -635,12 +764,16 @@ static int stm32_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
clk_rate_exclusive_put(i2s->i2smclk);
|
||||
i2s->mclk_rate = 0;
|
||||
}
|
||||
|
||||
if (i2s->put_i2s_clk_rate)
|
||||
i2s->put_i2s_clk_rate(i2s);
|
||||
|
||||
return regmap_update_bits(i2s->regmap,
|
||||
STM32_I2S_CGFR_REG,
|
||||
I2S_CGFR_MCKOE, 0);
|
||||
}
|
||||
/* If master clock is used, set parent clock now */
|
||||
ret = stm32_i2s_set_parent_clock(i2s, freq);
|
||||
ret = i2s->set_i2s_clk_rate(i2s, freq);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = clk_set_rate_exclusive(i2s->i2smclk, freq);
|
||||
@ -667,10 +800,11 @@ static int stm32_i2s_configure_clock(struct snd_soc_dai *cpu_dai,
|
||||
u32 cgfr;
|
||||
int ret;
|
||||
|
||||
if (!(rate % 11025))
|
||||
clk_set_parent(i2s->i2sclk, i2s->x11kclk);
|
||||
else
|
||||
clk_set_parent(i2s->i2sclk, i2s->x8kclk);
|
||||
if (!i2s->mclk_rate) {
|
||||
ret = i2s->set_i2s_clk_rate(i2s, rate);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
i2s_clock_rate = clk_get_rate(i2s->i2sclk);
|
||||
|
||||
/*
|
||||
@ -915,6 +1049,14 @@ static void stm32_i2s_shutdown(struct snd_pcm_substream *substream,
|
||||
|
||||
clk_disable_unprepare(i2s->i2sclk);
|
||||
|
||||
/*
|
||||
* Release kernel clock if following conditions are fulfilled
|
||||
* - Master clock is not used. Kernel clock won't be released trough sysclk
|
||||
* - Put handler is defined. Involve that clock is managed exclusively
|
||||
*/
|
||||
if (!i2s->i2smclk && i2s->put_i2s_clk_rate)
|
||||
i2s->put_i2s_clk_rate(i2s);
|
||||
|
||||
spin_lock_irqsave(&i2s->irq_lock, flags);
|
||||
i2s->substream = NULL;
|
||||
spin_unlock_irqrestore(&i2s->irq_lock, flags);
|
||||
@ -1012,14 +1154,36 @@ static int stm32_i2s_dais_init(struct platform_device *pdev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct stm32_i2s_conf stm32_i2s_conf_h7 = {
|
||||
.regmap_conf = &stm32_h7_i2s_regmap_conf,
|
||||
.get_i2s_clk_parent = stm32_i2s_get_parent_clk,
|
||||
};
|
||||
|
||||
static const struct stm32_i2s_conf stm32_i2s_conf_mp25 = {
|
||||
.regmap_conf = &stm32_h7_i2s_regmap_conf
|
||||
};
|
||||
|
||||
static const struct of_device_id stm32_i2s_ids[] = {
|
||||
{
|
||||
.compatible = "st,stm32h7-i2s",
|
||||
.data = &stm32_h7_i2s_regmap_conf
|
||||
},
|
||||
{ .compatible = "st,stm32h7-i2s", .data = &stm32_i2s_conf_h7 },
|
||||
{ .compatible = "st,stm32mp25-i2s", .data = &stm32_i2s_conf_mp25 },
|
||||
{},
|
||||
};
|
||||
|
||||
static int stm32_i2s_get_parent_clk(struct stm32_i2s_data *i2s)
|
||||
{
|
||||
struct device *dev = &i2s->pdev->dev;
|
||||
|
||||
i2s->x8kclk = devm_clk_get(dev, "x8k");
|
||||
if (IS_ERR(i2s->x8kclk))
|
||||
return dev_err_probe(dev, PTR_ERR(i2s->x8kclk), "Cannot get x8k parent clock\n");
|
||||
|
||||
i2s->x11kclk = devm_clk_get(dev, "x11k");
|
||||
if (IS_ERR(i2s->x11kclk))
|
||||
return dev_err_probe(dev, PTR_ERR(i2s->x11kclk), "Cannot get x11k parent clock\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_i2s_parse_dt(struct platform_device *pdev,
|
||||
struct stm32_i2s_data *i2s)
|
||||
{
|
||||
@ -1031,8 +1195,8 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev,
|
||||
if (!np)
|
||||
return -ENODEV;
|
||||
|
||||
i2s->regmap_conf = device_get_match_data(&pdev->dev);
|
||||
if (!i2s->regmap_conf)
|
||||
i2s->conf = device_get_match_data(&pdev->dev);
|
||||
if (!i2s->conf)
|
||||
return -EINVAL;
|
||||
|
||||
i2s->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
||||
@ -1052,15 +1216,18 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev,
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(i2s->i2sclk),
|
||||
"Could not get i2sclk\n");
|
||||
|
||||
i2s->x8kclk = devm_clk_get(&pdev->dev, "x8k");
|
||||
if (IS_ERR(i2s->x8kclk))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(i2s->x8kclk),
|
||||
"Could not get x8k parent clock\n");
|
||||
if (i2s->conf->get_i2s_clk_parent) {
|
||||
i2s->set_i2s_clk_rate = stm32_i2s_set_parent_clock;
|
||||
} else {
|
||||
i2s->set_i2s_clk_rate = stm32_i2s_set_parent_rate;
|
||||
i2s->put_i2s_clk_rate = stm32_i2s_put_parent_rate;
|
||||
}
|
||||
|
||||
i2s->x11kclk = devm_clk_get(&pdev->dev, "x11k");
|
||||
if (IS_ERR(i2s->x11kclk))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(i2s->x11kclk),
|
||||
"Could not get x11k parent clock\n");
|
||||
if (i2s->conf->get_i2s_clk_parent) {
|
||||
ret = i2s->conf->get_i2s_clk_parent(i2s);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Register mclk provider if requested */
|
||||
if (of_property_present(np, "#clock-cells")) {
|
||||
@ -1126,7 +1293,7 @@ static int stm32_i2s_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
|
||||
i2s->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "pclk",
|
||||
i2s->base, i2s->regmap_conf);
|
||||
i2s->base, i2s->conf->regmap_conf);
|
||||
if (IS_ERR(i2s->regmap))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap),
|
||||
"Regmap init error\n");
|
||||
|
Loading…
x
Reference in New Issue
Block a user