1 /*
2  * Copyright (c) 2022 Intel Corporation.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <errno.h>
8 #include <stdbool.h>
9 #include <stdint.h>
10 #include <zephyr/spinlock.h>
11 #include <zephyr/devicetree.h>
12 #include <zephyr/logging/log.h>
13 
14 #ifdef CONFIG_SOC_SERIES_INTEL_CAVS_V25
15 #include <adsp_shim.h>
16 #endif
17 
18 #define DT_DRV_COMPAT intel_alh_dai
19 #define LOG_DOMAIN dai_intel_alh
20 
21 LOG_MODULE_REGISTER(LOG_DOMAIN);
22 
23 #include "alh.h"
24 
25 /* global data shared between all alh instances */
26 struct dai_alh_global_shared dai_alh_global;
27 
28 /* Digital Audio interface formatting */
dai_alh_set_config_tplg(struct dai_intel_alh * dp,const void * spec_config)29 static int dai_alh_set_config_tplg(struct dai_intel_alh *dp, const void *spec_config)
30 {
31 	struct dai_intel_alh_pdata *alh = dai_get_drvdata(dp);
32 	const struct dai_intel_ipc3_alh_params *config = spec_config;
33 
34 	if (config->channels && config->rate) {
35 		alh->params.channels = config->channels;
36 		alh->params.rate = config->rate;
37 		LOG_INF("%s channels %d rate %d", __func__, config->channels, config->rate);
38 	}
39 
40 	alh->params.stream_id = config->stream_id;
41 
42 	return 0;
43 }
44 
dai_alh_set_config_blob(struct dai_intel_alh * dp,const struct dai_config * cfg,const void * spec_config)45 static int dai_alh_set_config_blob(struct dai_intel_alh *dp, const struct dai_config *cfg,
46 				   const void *spec_config)
47 {
48 	struct dai_intel_alh_pdata *alh = dai_get_drvdata(dp);
49 	const struct dai_intel_ipc4_alh_configuration_blob *blob = spec_config;
50 	const struct ipc4_alh_multi_gtw_cfg *alh_cfg = &blob->alh_cfg;
51 
52 	alh->params.rate = cfg->rate;
53 
54 	for (int i = 0; i < alh_cfg->count; i++) {
55 		/* the LSB 8bits are for stream id */
56 		int alh_id = alh_cfg->mapping[i].alh_id & 0xff;
57 
58 		if (IPC4_ALH_DAI_INDEX(alh_id) == dp->index) {
59 			alh->params.stream_id = alh_id;
60 			alh->params.channels = POPCOUNT(alh_cfg->mapping[i].channel_mask);
61 			break;
62 		}
63 	}
64 
65 	return 0;
66 }
67 
dai_alh_trigger(const struct device * dev,enum dai_dir dir,enum dai_trigger_cmd cmd)68 static int dai_alh_trigger(const struct device *dev, enum dai_dir dir,
69 			   enum dai_trigger_cmd cmd)
70 {
71 	LOG_DBG("cmd %d", cmd);
72 
73 	return 0;
74 }
75 
alh_claim_ownership(void)76 static void alh_claim_ownership(void)
77 {
78 #if CONFIG_DAI_ALH_HAS_OWNERSHIP
79 	uint32_t ALHASCTL = DT_INST_PROP_BY_IDX(0, reg, 0);
80 	uint32_t ALHCSCTL = DT_INST_PROP_BY_IDX(0, reg, 1);
81 
82 	sys_write32(sys_read32(ALHASCTL) | ALHASCTL_OSEL(0x3), ALHASCTL);
83 	sys_write32(sys_read32(ALHCSCTL) | ALHASCTL_OSEL(0x3), ALHCSCTL);
84 #endif
85 #ifdef CONFIG_SOC_SERIES_INTEL_CAVS_V25
86 	/* Allow LPGPDMA connection to Audio Link Hub */
87 	sys_set_bits(ADSP_DSPALHO_ADDRESS, DSPALHO_ASO_FLAG | DSPALHO_CSO_FLAG);
88 #endif
89 }
90 
alh_release_ownership(void)91 static void alh_release_ownership(void)
92 {
93 #if CONFIG_DAI_ALH_HAS_OWNERSHIP
94 	uint32_t ALHASCTL = DT_INST_PROP_BY_IDX(0, reg, 0);
95 	uint32_t ALHCSCTL = DT_INST_PROP_BY_IDX(0, reg, 1);
96 
97 	sys_write32(sys_read32(ALHASCTL) | ALHASCTL_OSEL(0), ALHASCTL);
98 	sys_write32(sys_read32(ALHCSCTL) | ALHASCTL_OSEL(0), ALHCSCTL);
99 #endif
100 #ifdef CONFIG_SOC_SERIES_INTEL_CAVS_V25
101 	sys_clear_bits(ADSP_DSPALHO_ADDRESS, DSPALHO_ASO_FLAG | DSPALHO_CSO_FLAG);
102 #endif
103 }
104 
105 
dai_alh_config_get(const struct device * dev,struct dai_config * cfg,enum dai_dir dir)106 static int dai_alh_config_get(const struct device *dev, struct dai_config *cfg,
107 			      enum dai_dir dir)
108 {
109 	struct dai_config *params = (struct dai_config *)dev->config;
110 	struct dai_intel_alh *dp = (struct dai_intel_alh *)dev->data;
111 	struct dai_intel_alh_pdata *alh = dai_get_drvdata(dp);
112 
113 	if (!cfg) {
114 		return -EINVAL;
115 	}
116 
117 	params->rate = alh->params.rate;
118 	params->channels = alh->params.channels;
119 	params->word_size = ALH_WORD_SIZE_DEFAULT;
120 
121 	*cfg = *params;
122 
123 	return 0;
124 }
125 
dai_alh_config_set(const struct device * dev,const struct dai_config * cfg,const void * bespoke_cfg)126 static int dai_alh_config_set(const struct device *dev, const struct dai_config *cfg,
127 				  const void *bespoke_cfg)
128 {
129 	struct dai_intel_alh *dp = (struct dai_intel_alh *)dev->data;
130 
131 	LOG_DBG("%s", __func__);
132 
133 	if (cfg->type == DAI_INTEL_ALH) {
134 		return dai_alh_set_config_tplg(dp, bespoke_cfg);
135 	} else {
136 		return dai_alh_set_config_blob(dp, cfg, bespoke_cfg);
137 	}
138 }
139 
dai_alh_get_properties(const struct device * dev,enum dai_dir dir,int stream_id)140 static const struct dai_properties *dai_alh_get_properties(const struct device *dev,
141 							   enum dai_dir dir, int stream_id)
142 {
143 	struct dai_intel_alh *dp = (struct dai_intel_alh *)dev->data;
144 	struct dai_intel_alh_pdata *alh = dai_get_drvdata(dp);
145 	struct dai_properties *prop = &alh->props;
146 	uint32_t offset = dir == DAI_DIR_PLAYBACK ?
147 		ALH_TXDA_OFFSET : ALH_RXDA_OFFSET;
148 
149 	prop->fifo_address = dai_base(dp) + offset + ALH_STREAM_OFFSET * stream_id;
150 	prop->fifo_depth = ALH_GPDMA_BURST_LENGTH;
151 	prop->dma_hs_id = alh_handshake_map[stream_id];
152 	prop->stream_id = alh->params.stream_id;
153 
154 	LOG_DBG("dai_index %u", dp->index);
155 	LOG_DBG("fifo %u", prop->fifo_address);
156 	LOG_DBG("handshake %u", prop->dma_hs_id);
157 
158 	return prop;
159 }
160 
dai_alh_probe(const struct device * dev)161 static int dai_alh_probe(const struct device *dev)
162 {
163 	k_spinlock_key_t key;
164 
165 	LOG_DBG("%s", __func__);
166 
167 	key = k_spin_lock(&dai_alh_global.lock);
168 
169 	if (dai_alh_global.sref == 0) {
170 		alh_claim_ownership();
171 	}
172 
173 	dai_alh_global.sref++;
174 
175 	k_spin_unlock(&dai_alh_global.lock, key);
176 
177 	return 0;
178 }
179 
dai_alh_remove(const struct device * dev)180 static int dai_alh_remove(const struct device *dev)
181 {
182 	k_spinlock_key_t key;
183 
184 	LOG_DBG("%s", __func__);
185 
186 	key = k_spin_lock(&dai_alh_global.lock);
187 
188 	if (--dai_alh_global.sref == 0) {
189 		alh_release_ownership();
190 	}
191 
192 	k_spin_unlock(&dai_alh_global.lock, key);
193 
194 	return 0;
195 }
196 
197 static DEVICE_API(dai, dai_intel_alh_api_funcs) = {
198 	.probe			= dai_alh_probe,
199 	.remove			= dai_alh_remove,
200 	.config_set		= dai_alh_config_set,
201 	.config_get		= dai_alh_config_get,
202 	.trigger		= dai_alh_trigger,
203 	.get_properties		= dai_alh_get_properties,
204 };
205 
206 #define DAI_INTEL_ALH_DEVICE_INIT(n)						\
207 	static struct dai_config dai_intel_alh_config_##n = {			\
208 		.type = DAI_INTEL_ALH,						\
209 		.dai_index = (n / DAI_NUM_ALH_BI_DIR_LINKS_GROUP) << 8 |	\
210 			(n % DAI_NUM_ALH_BI_DIR_LINKS_GROUP),			\
211 	};									\
212 	static struct dai_intel_alh dai_intel_alh_data_##n = {			\
213 		.index = (n / DAI_NUM_ALH_BI_DIR_LINKS_GROUP) << 8 |		\
214 			(n % DAI_NUM_ALH_BI_DIR_LINKS_GROUP),			\
215 		.plat_data = {							\
216 			.base =	DT_INST_PROP_BY_IDX(n, reg, 0),			\
217 			.fifo_depth[DAI_DIR_PLAYBACK] =	ALH_GPDMA_BURST_LENGTH,		\
218 			.fifo_depth[DAI_DIR_CAPTURE] = ALH_GPDMA_BURST_LENGTH,		\
219 		},								\
220 	};								\
221 									\
222 	DEVICE_DT_INST_DEFINE(n,					\
223 			NULL, NULL,					\
224 			&dai_intel_alh_data_##n,			\
225 			&dai_intel_alh_config_##n,			\
226 			POST_KERNEL, 32,				\
227 			&dai_intel_alh_api_funcs);
228 
229 DT_INST_FOREACH_STATUS_OKAY(DAI_INTEL_ALH_DEVICE_INIT)
230