162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0 OR MIT)
262306a36Sopenharmony_ci//
362306a36Sopenharmony_ci// Copyright (c) 2018 BayLibre, SAS.
462306a36Sopenharmony_ci// Author: Jerome Brunet <jbrunet@baylibre.com>
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/clk.h>
762306a36Sopenharmony_ci#include <linux/module.h>
862306a36Sopenharmony_ci#include <linux/of_platform.h>
962306a36Sopenharmony_ci#include <linux/regmap.h>
1062306a36Sopenharmony_ci#include <linux/reset.h>
1162306a36Sopenharmony_ci#include <sound/soc.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "axg-tdm-formatter.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistruct axg_tdm_formatter {
1662306a36Sopenharmony_ci	struct list_head list;
1762306a36Sopenharmony_ci	struct axg_tdm_stream *stream;
1862306a36Sopenharmony_ci	const struct axg_tdm_formatter_driver *drv;
1962306a36Sopenharmony_ci	struct clk *pclk;
2062306a36Sopenharmony_ci	struct clk *sclk;
2162306a36Sopenharmony_ci	struct clk *lrclk;
2262306a36Sopenharmony_ci	struct clk *sclk_sel;
2362306a36Sopenharmony_ci	struct clk *lrclk_sel;
2462306a36Sopenharmony_ci	struct reset_control *reset;
2562306a36Sopenharmony_ci	bool enabled;
2662306a36Sopenharmony_ci	struct regmap *map;
2762306a36Sopenharmony_ci};
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ciint axg_tdm_formatter_set_channel_masks(struct regmap *map,
3062306a36Sopenharmony_ci					struct axg_tdm_stream *ts,
3162306a36Sopenharmony_ci					unsigned int offset)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	unsigned int ch = ts->channels;
3462306a36Sopenharmony_ci	u32 val[AXG_TDM_NUM_LANES];
3562306a36Sopenharmony_ci	int i, j, k;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	/*
3862306a36Sopenharmony_ci	 * We need to mimick the slot distribution used by the HW to keep the
3962306a36Sopenharmony_ci	 * channel placement consistent regardless of the number of channel
4062306a36Sopenharmony_ci	 * in the stream. This is why the odd algorithm below is used.
4162306a36Sopenharmony_ci	 */
4262306a36Sopenharmony_ci	memset(val, 0, sizeof(*val) * AXG_TDM_NUM_LANES);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	/*
4562306a36Sopenharmony_ci	 * Distribute the channels of the stream over the available slots
4662306a36Sopenharmony_ci	 * of each TDM lane. We need to go over the 32 slots ...
4762306a36Sopenharmony_ci	 */
4862306a36Sopenharmony_ci	for (i = 0; (i < 32) && ch; i += 2) {
4962306a36Sopenharmony_ci		/* ... of all the lanes ... */
5062306a36Sopenharmony_ci		for (j = 0; j < AXG_TDM_NUM_LANES; j++) {
5162306a36Sopenharmony_ci			/* ... then distribute the channels in pairs */
5262306a36Sopenharmony_ci			for (k = 0; k < 2; k++) {
5362306a36Sopenharmony_ci				if ((BIT(i + k) & ts->mask[j]) && ch) {
5462306a36Sopenharmony_ci					val[j] |= BIT(i + k);
5562306a36Sopenharmony_ci					ch -= 1;
5662306a36Sopenharmony_ci				}
5762306a36Sopenharmony_ci			}
5862306a36Sopenharmony_ci		}
5962306a36Sopenharmony_ci	}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	/*
6262306a36Sopenharmony_ci	 * If we still have channel left at the end of the process, it means
6362306a36Sopenharmony_ci	 * the stream has more channels than we can accommodate and we should
6462306a36Sopenharmony_ci	 * have caught this earlier.
6562306a36Sopenharmony_ci	 */
6662306a36Sopenharmony_ci	if (WARN_ON(ch != 0)) {
6762306a36Sopenharmony_ci		pr_err("channel mask error\n");
6862306a36Sopenharmony_ci		return -EINVAL;
6962306a36Sopenharmony_ci	}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	for (i = 0; i < AXG_TDM_NUM_LANES; i++) {
7262306a36Sopenharmony_ci		regmap_write(map, offset, val[i]);
7362306a36Sopenharmony_ci		offset += regmap_get_reg_stride(map);
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	return 0;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(axg_tdm_formatter_set_channel_masks);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic int axg_tdm_formatter_enable(struct axg_tdm_formatter *formatter)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	struct axg_tdm_stream *ts = formatter->stream;
8362306a36Sopenharmony_ci	bool invert;
8462306a36Sopenharmony_ci	int ret;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	/* Do nothing if the formatter is already enabled */
8762306a36Sopenharmony_ci	if (formatter->enabled)
8862306a36Sopenharmony_ci		return 0;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	/*
9162306a36Sopenharmony_ci	 * On the g12a (and possibly other SoCs), when a stream using
9262306a36Sopenharmony_ci	 * multiple lanes is restarted, it will sometimes not start
9362306a36Sopenharmony_ci	 * from the first lane, but randomly from another used one.
9462306a36Sopenharmony_ci	 * The result is an unexpected and random channel shift.
9562306a36Sopenharmony_ci	 *
9662306a36Sopenharmony_ci	 * The hypothesis is that an HW counter is not properly reset
9762306a36Sopenharmony_ci	 * and the formatter simply starts on the lane it stopped
9862306a36Sopenharmony_ci	 * before. Unfortunately, there does not seems to be a way to
9962306a36Sopenharmony_ci	 * reset this through the registers of the block.
10062306a36Sopenharmony_ci	 *
10162306a36Sopenharmony_ci	 * However, the g12a has indenpendent reset lines for each audio
10262306a36Sopenharmony_ci	 * devices. Using this reset before each start solves the issue.
10362306a36Sopenharmony_ci	 */
10462306a36Sopenharmony_ci	ret = reset_control_reset(formatter->reset);
10562306a36Sopenharmony_ci	if (ret)
10662306a36Sopenharmony_ci		return ret;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	/*
10962306a36Sopenharmony_ci	 * If sclk is inverted, it means the bit should latched on the
11062306a36Sopenharmony_ci	 * rising edge which is what our HW expects. If not, we need to
11162306a36Sopenharmony_ci	 * invert it before the formatter.
11262306a36Sopenharmony_ci	 */
11362306a36Sopenharmony_ci	invert = axg_tdm_sclk_invert(ts->iface->fmt);
11462306a36Sopenharmony_ci	ret = clk_set_phase(formatter->sclk, invert ? 0 : 180);
11562306a36Sopenharmony_ci	if (ret)
11662306a36Sopenharmony_ci		return ret;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/* Setup the stream parameter in the formatter */
11962306a36Sopenharmony_ci	ret = formatter->drv->ops->prepare(formatter->map,
12062306a36Sopenharmony_ci					   formatter->drv->quirks,
12162306a36Sopenharmony_ci					   formatter->stream);
12262306a36Sopenharmony_ci	if (ret)
12362306a36Sopenharmony_ci		return ret;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	/* Enable the signal clocks feeding the formatter */
12662306a36Sopenharmony_ci	ret = clk_prepare_enable(formatter->sclk);
12762306a36Sopenharmony_ci	if (ret)
12862306a36Sopenharmony_ci		return ret;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	ret = clk_prepare_enable(formatter->lrclk);
13162306a36Sopenharmony_ci	if (ret) {
13262306a36Sopenharmony_ci		clk_disable_unprepare(formatter->sclk);
13362306a36Sopenharmony_ci		return ret;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/* Finally, actually enable the formatter */
13762306a36Sopenharmony_ci	formatter->drv->ops->enable(formatter->map);
13862306a36Sopenharmony_ci	formatter->enabled = true;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	return 0;
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic void axg_tdm_formatter_disable(struct axg_tdm_formatter *formatter)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	/* Do nothing if the formatter is already disabled */
14662306a36Sopenharmony_ci	if (!formatter->enabled)
14762306a36Sopenharmony_ci		return;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	formatter->drv->ops->disable(formatter->map);
15062306a36Sopenharmony_ci	clk_disable_unprepare(formatter->lrclk);
15162306a36Sopenharmony_ci	clk_disable_unprepare(formatter->sclk);
15262306a36Sopenharmony_ci	formatter->enabled = false;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic int axg_tdm_formatter_attach(struct axg_tdm_formatter *formatter)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	struct axg_tdm_stream *ts = formatter->stream;
15862306a36Sopenharmony_ci	int ret = 0;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	mutex_lock(&ts->lock);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	/* Catch up if the stream is already running when we attach */
16362306a36Sopenharmony_ci	if (ts->ready) {
16462306a36Sopenharmony_ci		ret = axg_tdm_formatter_enable(formatter);
16562306a36Sopenharmony_ci		if (ret) {
16662306a36Sopenharmony_ci			pr_err("failed to enable formatter\n");
16762306a36Sopenharmony_ci			goto out;
16862306a36Sopenharmony_ci		}
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	list_add_tail(&formatter->list, &ts->formatter_list);
17262306a36Sopenharmony_ciout:
17362306a36Sopenharmony_ci	mutex_unlock(&ts->lock);
17462306a36Sopenharmony_ci	return ret;
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic void axg_tdm_formatter_dettach(struct axg_tdm_formatter *formatter)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	struct axg_tdm_stream *ts = formatter->stream;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	mutex_lock(&ts->lock);
18262306a36Sopenharmony_ci	list_del(&formatter->list);
18362306a36Sopenharmony_ci	mutex_unlock(&ts->lock);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	axg_tdm_formatter_disable(formatter);
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cistatic int axg_tdm_formatter_power_up(struct axg_tdm_formatter *formatter,
18962306a36Sopenharmony_ci				      struct snd_soc_dapm_widget *w)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	struct axg_tdm_stream *ts = formatter->drv->ops->get_stream(w);
19262306a36Sopenharmony_ci	int ret;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	/*
19562306a36Sopenharmony_ci	 * If we don't get a stream at this stage, it would mean that the
19662306a36Sopenharmony_ci	 * widget is powering up but is not attached to any backend DAI.
19762306a36Sopenharmony_ci	 * It should not happen, ever !
19862306a36Sopenharmony_ci	 */
19962306a36Sopenharmony_ci	if (WARN_ON(!ts))
20062306a36Sopenharmony_ci		return -ENODEV;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	/* Clock our device */
20362306a36Sopenharmony_ci	ret = clk_prepare_enable(formatter->pclk);
20462306a36Sopenharmony_ci	if (ret)
20562306a36Sopenharmony_ci		return ret;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	/* Reparent the bit clock to the TDM interface */
20862306a36Sopenharmony_ci	ret = clk_set_parent(formatter->sclk_sel, ts->iface->sclk);
20962306a36Sopenharmony_ci	if (ret)
21062306a36Sopenharmony_ci		goto disable_pclk;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	/* Reparent the sample clock to the TDM interface */
21362306a36Sopenharmony_ci	ret = clk_set_parent(formatter->lrclk_sel, ts->iface->lrclk);
21462306a36Sopenharmony_ci	if (ret)
21562306a36Sopenharmony_ci		goto disable_pclk;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	formatter->stream = ts;
21862306a36Sopenharmony_ci	ret = axg_tdm_formatter_attach(formatter);
21962306a36Sopenharmony_ci	if (ret)
22062306a36Sopenharmony_ci		goto disable_pclk;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return 0;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cidisable_pclk:
22562306a36Sopenharmony_ci	clk_disable_unprepare(formatter->pclk);
22662306a36Sopenharmony_ci	return ret;
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic void axg_tdm_formatter_power_down(struct axg_tdm_formatter *formatter)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	axg_tdm_formatter_dettach(formatter);
23262306a36Sopenharmony_ci	clk_disable_unprepare(formatter->pclk);
23362306a36Sopenharmony_ci	formatter->stream = NULL;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ciint axg_tdm_formatter_event(struct snd_soc_dapm_widget *w,
23762306a36Sopenharmony_ci			    struct snd_kcontrol *control,
23862306a36Sopenharmony_ci			    int event)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
24162306a36Sopenharmony_ci	struct axg_tdm_formatter *formatter = snd_soc_component_get_drvdata(c);
24262306a36Sopenharmony_ci	int ret = 0;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	switch (event) {
24562306a36Sopenharmony_ci	case SND_SOC_DAPM_PRE_PMU:
24662306a36Sopenharmony_ci		ret = axg_tdm_formatter_power_up(formatter, w);
24762306a36Sopenharmony_ci		break;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	case SND_SOC_DAPM_PRE_PMD:
25062306a36Sopenharmony_ci		axg_tdm_formatter_power_down(formatter);
25162306a36Sopenharmony_ci		break;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	default:
25462306a36Sopenharmony_ci		dev_err(c->dev, "Unexpected event %d\n", event);
25562306a36Sopenharmony_ci		return -EINVAL;
25662306a36Sopenharmony_ci	}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	return ret;
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(axg_tdm_formatter_event);
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ciint axg_tdm_formatter_probe(struct platform_device *pdev)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
26562306a36Sopenharmony_ci	const struct axg_tdm_formatter_driver *drv;
26662306a36Sopenharmony_ci	struct axg_tdm_formatter *formatter;
26762306a36Sopenharmony_ci	void __iomem *regs;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	drv = of_device_get_match_data(dev);
27062306a36Sopenharmony_ci	if (!drv) {
27162306a36Sopenharmony_ci		dev_err(dev, "failed to match device\n");
27262306a36Sopenharmony_ci		return -ENODEV;
27362306a36Sopenharmony_ci	}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
27662306a36Sopenharmony_ci	if (!formatter)
27762306a36Sopenharmony_ci		return -ENOMEM;
27862306a36Sopenharmony_ci	platform_set_drvdata(pdev, formatter);
27962306a36Sopenharmony_ci	formatter->drv = drv;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	regs = devm_platform_ioremap_resource(pdev, 0);
28262306a36Sopenharmony_ci	if (IS_ERR(regs))
28362306a36Sopenharmony_ci		return PTR_ERR(regs);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg);
28662306a36Sopenharmony_ci	if (IS_ERR(formatter->map)) {
28762306a36Sopenharmony_ci		dev_err(dev, "failed to init regmap: %ld\n",
28862306a36Sopenharmony_ci			PTR_ERR(formatter->map));
28962306a36Sopenharmony_ci		return PTR_ERR(formatter->map);
29062306a36Sopenharmony_ci	}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	/* Peripharal clock */
29362306a36Sopenharmony_ci	formatter->pclk = devm_clk_get(dev, "pclk");
29462306a36Sopenharmony_ci	if (IS_ERR(formatter->pclk))
29562306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(formatter->pclk), "failed to get pclk\n");
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	/* Formatter bit clock */
29862306a36Sopenharmony_ci	formatter->sclk = devm_clk_get(dev, "sclk");
29962306a36Sopenharmony_ci	if (IS_ERR(formatter->sclk))
30062306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(formatter->sclk), "failed to get sclk\n");
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	/* Formatter sample clock */
30362306a36Sopenharmony_ci	formatter->lrclk = devm_clk_get(dev, "lrclk");
30462306a36Sopenharmony_ci	if (IS_ERR(formatter->lrclk))
30562306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(formatter->lrclk), "failed to get lrclk\n");
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	/* Formatter bit clock input multiplexer */
30862306a36Sopenharmony_ci	formatter->sclk_sel = devm_clk_get(dev, "sclk_sel");
30962306a36Sopenharmony_ci	if (IS_ERR(formatter->sclk_sel))
31062306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(formatter->sclk_sel), "failed to get sclk_sel\n");
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	/* Formatter sample clock input multiplexer */
31362306a36Sopenharmony_ci	formatter->lrclk_sel = devm_clk_get(dev, "lrclk_sel");
31462306a36Sopenharmony_ci	if (IS_ERR(formatter->lrclk_sel))
31562306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(formatter->lrclk_sel),
31662306a36Sopenharmony_ci				     "failed to get lrclk_sel\n");
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	/* Formatter dedicated reset line */
31962306a36Sopenharmony_ci	formatter->reset = devm_reset_control_get_optional_exclusive(dev, NULL);
32062306a36Sopenharmony_ci	if (IS_ERR(formatter->reset))
32162306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(formatter->reset), "failed to get reset\n");
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	return devm_snd_soc_register_component(dev, drv->component_drv,
32462306a36Sopenharmony_ci					       NULL, 0);
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(axg_tdm_formatter_probe);
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ciint axg_tdm_stream_start(struct axg_tdm_stream *ts)
32962306a36Sopenharmony_ci{
33062306a36Sopenharmony_ci	struct axg_tdm_formatter *formatter;
33162306a36Sopenharmony_ci	int ret = 0;
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	mutex_lock(&ts->lock);
33462306a36Sopenharmony_ci	ts->ready = true;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	/* Start all the formatters attached to the stream */
33762306a36Sopenharmony_ci	list_for_each_entry(formatter, &ts->formatter_list, list) {
33862306a36Sopenharmony_ci		ret = axg_tdm_formatter_enable(formatter);
33962306a36Sopenharmony_ci		if (ret) {
34062306a36Sopenharmony_ci			pr_err("failed to start tdm stream\n");
34162306a36Sopenharmony_ci			goto out;
34262306a36Sopenharmony_ci		}
34362306a36Sopenharmony_ci	}
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ciout:
34662306a36Sopenharmony_ci	mutex_unlock(&ts->lock);
34762306a36Sopenharmony_ci	return ret;
34862306a36Sopenharmony_ci}
34962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(axg_tdm_stream_start);
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_civoid axg_tdm_stream_stop(struct axg_tdm_stream *ts)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	struct axg_tdm_formatter *formatter;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	mutex_lock(&ts->lock);
35662306a36Sopenharmony_ci	ts->ready = false;
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	/* Stop all the formatters attached to the stream */
35962306a36Sopenharmony_ci	list_for_each_entry(formatter, &ts->formatter_list, list) {
36062306a36Sopenharmony_ci		axg_tdm_formatter_disable(formatter);
36162306a36Sopenharmony_ci	}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	mutex_unlock(&ts->lock);
36462306a36Sopenharmony_ci}
36562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(axg_tdm_stream_stop);
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_cistruct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface)
36862306a36Sopenharmony_ci{
36962306a36Sopenharmony_ci	struct axg_tdm_stream *ts;
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
37262306a36Sopenharmony_ci	if (ts) {
37362306a36Sopenharmony_ci		INIT_LIST_HEAD(&ts->formatter_list);
37462306a36Sopenharmony_ci		mutex_init(&ts->lock);
37562306a36Sopenharmony_ci		ts->iface = iface;
37662306a36Sopenharmony_ci	}
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	return ts;
37962306a36Sopenharmony_ci}
38062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(axg_tdm_stream_alloc);
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_civoid axg_tdm_stream_free(struct axg_tdm_stream *ts)
38362306a36Sopenharmony_ci{
38462306a36Sopenharmony_ci	/*
38562306a36Sopenharmony_ci	 * If the list is not empty, it would mean that one of the formatter
38662306a36Sopenharmony_ci	 * widget is still powered and attached to the interface while we
38762306a36Sopenharmony_ci	 * are removing the TDM DAI. It should not be possible
38862306a36Sopenharmony_ci	 */
38962306a36Sopenharmony_ci	WARN_ON(!list_empty(&ts->formatter_list));
39062306a36Sopenharmony_ci	mutex_destroy(&ts->lock);
39162306a36Sopenharmony_ci	kfree(ts);
39262306a36Sopenharmony_ci}
39362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(axg_tdm_stream_free);
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ciMODULE_DESCRIPTION("Amlogic AXG TDM formatter driver");
39662306a36Sopenharmony_ciMODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
39762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
398