1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
2 | // |
3 | // Copyright (c) 2020 BayLibre, SAS. |
4 | // Author: Jerome Brunet <jbrunet@baylibre.com> |
5 | |
6 | #include <linux/module.h> |
7 | #include <linux/of_platform.h> |
8 | #include <sound/soc.h> |
9 | #include <sound/soc-dai.h> |
10 | |
11 | #include "meson-card.h" |
12 | |
13 | struct gx_dai_link_i2s_data { |
14 | unsigned int mclk_fs; |
15 | }; |
16 | |
17 | /* |
18 | * Base params for the codec to codec links |
19 | * Those will be over-written by the CPU side of the link |
20 | */ |
21 | static const struct snd_soc_pcm_stream codec_params = { |
22 | .formats = SNDRV_PCM_FMTBIT_S24_LE, |
23 | .rate_min = 5525, |
24 | .rate_max = 192000, |
25 | .channels_min = 1, |
26 | .channels_max = 8, |
27 | }; |
28 | |
29 | static int gx_card_i2s_be_hw_params(struct snd_pcm_substream *substream, |
30 | struct snd_pcm_hw_params *params) |
31 | { |
32 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
33 | struct meson_card *priv = snd_soc_card_get_drvdata(card: rtd->card); |
34 | struct gx_dai_link_i2s_data *be = |
35 | (struct gx_dai_link_i2s_data *)priv->link_data[rtd->num]; |
36 | |
37 | return meson_card_i2s_set_sysclk(substream, params, mclk_fs: be->mclk_fs); |
38 | } |
39 | |
40 | static const struct snd_soc_ops gx_card_i2s_be_ops = { |
41 | .hw_params = gx_card_i2s_be_hw_params, |
42 | }; |
43 | |
44 | static int gx_card_parse_i2s(struct snd_soc_card *card, |
45 | struct device_node *node, |
46 | int *index) |
47 | { |
48 | struct meson_card *priv = snd_soc_card_get_drvdata(card); |
49 | struct snd_soc_dai_link *link = &card->dai_link[*index]; |
50 | struct gx_dai_link_i2s_data *be; |
51 | |
52 | /* Allocate i2s link parameters */ |
53 | be = devm_kzalloc(dev: card->dev, size: sizeof(*be), GFP_KERNEL); |
54 | if (!be) |
55 | return -ENOMEM; |
56 | priv->link_data[*index] = be; |
57 | |
58 | /* Setup i2s link */ |
59 | link->ops = &gx_card_i2s_be_ops; |
60 | link->dai_fmt = meson_card_parse_daifmt(node, cpu_node: link->cpus->of_node); |
61 | |
62 | of_property_read_u32(np: node, propname: "mclk-fs" , out_value: &be->mclk_fs); |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static int gx_card_cpu_identify(struct snd_soc_dai_link_component *c, |
68 | char *match) |
69 | { |
70 | if (of_device_is_compatible(device: c->of_node, DT_PREFIX "aiu" )) { |
71 | if (strstr(c->dai_name, match)) |
72 | return 1; |
73 | } |
74 | |
75 | /* dai not matched */ |
76 | return 0; |
77 | } |
78 | |
79 | static int gx_card_add_link(struct snd_soc_card *card, struct device_node *np, |
80 | int *index) |
81 | { |
82 | struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; |
83 | struct snd_soc_dai_link_component *cpu; |
84 | int ret; |
85 | |
86 | cpu = devm_kzalloc(dev: card->dev, size: sizeof(*cpu), GFP_KERNEL); |
87 | if (!cpu) |
88 | return -ENOMEM; |
89 | |
90 | dai_link->cpus = cpu; |
91 | dai_link->num_cpus = 1; |
92 | |
93 | ret = meson_card_parse_dai(card, node: np, dlc: dai_link->cpus); |
94 | if (ret) |
95 | return ret; |
96 | |
97 | if (gx_card_cpu_identify(c: dai_link->cpus, match: "FIFO" )) |
98 | return meson_card_set_fe_link(card, link: dai_link, node: np, is_playback: true); |
99 | |
100 | ret = meson_card_set_be_link(card, link: dai_link, node: np); |
101 | if (ret) |
102 | return ret; |
103 | |
104 | /* Or apply codec to codec params if necessary */ |
105 | if (gx_card_cpu_identify(c: dai_link->cpus, match: "CODEC CTRL" )) { |
106 | dai_link->c2c_params = &codec_params; |
107 | dai_link->num_c2c_params = 1; |
108 | } else { |
109 | dai_link->no_pcm = 1; |
110 | snd_soc_dai_link_set_capabilities(dai_link); |
111 | /* Check if the cpu is the i2s encoder and parse i2s data */ |
112 | if (gx_card_cpu_identify(c: dai_link->cpus, match: "I2S Encoder" )) |
113 | ret = gx_card_parse_i2s(card, node: np, index); |
114 | } |
115 | |
116 | return ret; |
117 | } |
118 | |
119 | static const struct meson_card_match_data gx_card_match_data = { |
120 | .add_link = gx_card_add_link, |
121 | }; |
122 | |
123 | static const struct of_device_id gx_card_of_match[] = { |
124 | { |
125 | .compatible = "amlogic,gx-sound-card" , |
126 | .data = &gx_card_match_data, |
127 | }, {} |
128 | }; |
129 | MODULE_DEVICE_TABLE(of, gx_card_of_match); |
130 | |
131 | static struct platform_driver gx_card_pdrv = { |
132 | .probe = meson_card_probe, |
133 | .remove_new = meson_card_remove, |
134 | .driver = { |
135 | .name = "gx-sound-card" , |
136 | .of_match_table = gx_card_of_match, |
137 | }, |
138 | }; |
139 | module_platform_driver(gx_card_pdrv); |
140 | |
141 | MODULE_DESCRIPTION("Amlogic GX ALSA machine driver" ); |
142 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>" ); |
143 | MODULE_LICENSE("GPL v2" ); |
144 | |