Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/soc/meson/aiu-encoder-spdif.c
26436 views
1
// SPDX-License-Identifier: GPL-2.0
2
//
3
// Copyright (c) 2020 BayLibre, SAS.
4
// Author: Jerome Brunet <[email protected]>
5
6
#include <linux/bitfield.h>
7
#include <linux/clk.h>
8
#include <sound/pcm_params.h>
9
#include <sound/pcm_iec958.h>
10
#include <sound/soc.h>
11
#include <sound/soc-dai.h>
12
13
#include "aiu.h"
14
15
#define AIU_958_MISC_NON_PCM BIT(0)
16
#define AIU_958_MISC_MODE_16BITS BIT(1)
17
#define AIU_958_MISC_16BITS_ALIGN GENMASK(6, 5)
18
#define AIU_958_MISC_MODE_32BITS BIT(7)
19
#define AIU_958_MISC_U_FROM_STREAM BIT(12)
20
#define AIU_958_MISC_FORCE_LR BIT(13)
21
#define AIU_958_CTRL_HOLD_EN BIT(0)
22
#define AIU_CLK_CTRL_958_DIV_EN BIT(1)
23
#define AIU_CLK_CTRL_958_DIV GENMASK(5, 4)
24
#define AIU_CLK_CTRL_958_DIV_MORE BIT(12)
25
26
#define AIU_CS_WORD_LEN 4
27
#define AIU_958_INTERNAL_DIV 2
28
29
static void
30
aiu_encoder_spdif_divider_enable(struct snd_soc_component *component,
31
bool enable)
32
{
33
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
34
AIU_CLK_CTRL_958_DIV_EN,
35
enable ? AIU_CLK_CTRL_958_DIV_EN : 0);
36
}
37
38
static void aiu_encoder_spdif_hold(struct snd_soc_component *component,
39
bool enable)
40
{
41
snd_soc_component_update_bits(component, AIU_958_CTRL,
42
AIU_958_CTRL_HOLD_EN,
43
enable ? AIU_958_CTRL_HOLD_EN : 0);
44
}
45
46
static int
47
aiu_encoder_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
48
struct snd_soc_dai *dai)
49
{
50
struct snd_soc_component *component = dai->component;
51
52
switch (cmd) {
53
case SNDRV_PCM_TRIGGER_START:
54
case SNDRV_PCM_TRIGGER_RESUME:
55
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
56
aiu_encoder_spdif_hold(component, false);
57
return 0;
58
59
case SNDRV_PCM_TRIGGER_STOP:
60
case SNDRV_PCM_TRIGGER_SUSPEND:
61
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
62
aiu_encoder_spdif_hold(component, true);
63
return 0;
64
65
default:
66
return -EINVAL;
67
}
68
}
69
70
static int aiu_encoder_spdif_setup_cs_word(struct snd_soc_component *component,
71
struct snd_pcm_hw_params *params)
72
{
73
u8 cs[AIU_CS_WORD_LEN];
74
unsigned int val;
75
int ret;
76
77
ret = snd_pcm_create_iec958_consumer_hw_params(params, cs,
78
AIU_CS_WORD_LEN);
79
if (ret < 0)
80
return ret;
81
82
/* Write the 1st half word */
83
val = cs[1] | cs[0] << 8;
84
snd_soc_component_write(component, AIU_958_CHSTAT_L0, val);
85
snd_soc_component_write(component, AIU_958_CHSTAT_R0, val);
86
87
/* Write the 2nd half word */
88
val = cs[3] | cs[2] << 8;
89
snd_soc_component_write(component, AIU_958_CHSTAT_L1, val);
90
snd_soc_component_write(component, AIU_958_CHSTAT_R1, val);
91
92
return 0;
93
}
94
95
static int aiu_encoder_spdif_hw_params(struct snd_pcm_substream *substream,
96
struct snd_pcm_hw_params *params,
97
struct snd_soc_dai *dai)
98
{
99
struct snd_soc_component *component = dai->component;
100
struct aiu *aiu = snd_soc_component_get_drvdata(component);
101
unsigned int val = 0, mrate;
102
int ret;
103
104
/* Disable the clock while changing the settings */
105
aiu_encoder_spdif_divider_enable(component, false);
106
107
switch (params_physical_width(params)) {
108
case 16:
109
val |= AIU_958_MISC_MODE_16BITS;
110
val |= FIELD_PREP(AIU_958_MISC_16BITS_ALIGN, 2);
111
break;
112
case 32:
113
val |= AIU_958_MISC_MODE_32BITS;
114
break;
115
default:
116
dev_err(dai->dev, "Unsupported physical width\n");
117
return -EINVAL;
118
}
119
120
snd_soc_component_update_bits(component, AIU_958_MISC,
121
AIU_958_MISC_NON_PCM |
122
AIU_958_MISC_MODE_16BITS |
123
AIU_958_MISC_16BITS_ALIGN |
124
AIU_958_MISC_MODE_32BITS |
125
AIU_958_MISC_FORCE_LR |
126
AIU_958_MISC_U_FROM_STREAM,
127
val);
128
129
/* Set the stream channel status word */
130
ret = aiu_encoder_spdif_setup_cs_word(component, params);
131
if (ret) {
132
dev_err(dai->dev, "failed to set channel status word\n");
133
return ret;
134
}
135
136
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
137
AIU_CLK_CTRL_958_DIV |
138
AIU_CLK_CTRL_958_DIV_MORE,
139
FIELD_PREP(AIU_CLK_CTRL_958_DIV,
140
__ffs(AIU_958_INTERNAL_DIV)));
141
142
/* 2 * 32bits per subframe * 2 channels = 128 */
143
mrate = params_rate(params) * 128 * AIU_958_INTERNAL_DIV;
144
ret = clk_set_rate(aiu->spdif.clks[MCLK].clk, mrate);
145
if (ret) {
146
dev_err(dai->dev, "failed to set mclk rate\n");
147
return ret;
148
}
149
150
aiu_encoder_spdif_divider_enable(component, true);
151
152
return 0;
153
}
154
155
static int aiu_encoder_spdif_hw_free(struct snd_pcm_substream *substream,
156
struct snd_soc_dai *dai)
157
{
158
struct snd_soc_component *component = dai->component;
159
160
aiu_encoder_spdif_divider_enable(component, false);
161
162
return 0;
163
}
164
165
static int aiu_encoder_spdif_startup(struct snd_pcm_substream *substream,
166
struct snd_soc_dai *dai)
167
{
168
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
169
int ret;
170
171
/*
172
* NOTE: Make sure the spdif block is on its own divider.
173
*
174
* The spdif can be clocked by the i2s master clock or its own
175
* clock. We should (in theory) change the source depending on the
176
* origin of the data.
177
*
178
* However, considering the clocking scheme used on these platforms,
179
* the master clocks will pick the same PLL source when they are
180
* playing from the same FIFO. The clock should be in sync so, it
181
* should not be necessary to reparent the spdif master clock.
182
*/
183
ret = clk_set_parent(aiu->spdif.clks[MCLK].clk,
184
aiu->spdif_mclk);
185
if (ret)
186
return ret;
187
188
ret = clk_bulk_prepare_enable(aiu->spdif.clk_num, aiu->spdif.clks);
189
if (ret)
190
dev_err(dai->dev, "failed to enable spdif clocks\n");
191
192
return ret;
193
}
194
195
static void aiu_encoder_spdif_shutdown(struct snd_pcm_substream *substream,
196
struct snd_soc_dai *dai)
197
{
198
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
199
200
clk_bulk_disable_unprepare(aiu->spdif.clk_num, aiu->spdif.clks);
201
}
202
203
const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops = {
204
.trigger = aiu_encoder_spdif_trigger,
205
.hw_params = aiu_encoder_spdif_hw_params,
206
.hw_free = aiu_encoder_spdif_hw_free,
207
.startup = aiu_encoder_spdif_startup,
208
.shutdown = aiu_encoder_spdif_shutdown,
209
};
210
211