Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.9.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * CS40L50 Advanced Haptic Driver with waveform memory,
  4 * integrated DSP, and closed-loop algorithms
  5 *
  6 * Copyright 2024 Cirrus Logic, Inc.
  7 *
  8 * Author: James Ogletree <james.ogletree@cirrus.com>
  9 */
 10
 11#include <linux/firmware/cirrus/cs_dsp.h>
 12#include <linux/firmware/cirrus/wmfw.h>
 13#include <linux/mfd/core.h>
 14#include <linux/mfd/cs40l50.h>
 15#include <linux/pm_runtime.h>
 16#include <linux/regulator/consumer.h>
 17
 18static const struct mfd_cell cs40l50_devs[] = {
 19	{ .name = "cs40l50-codec", },
 20	{ .name = "cs40l50-vibra", },
 21};
 22
 23const struct regmap_config cs40l50_regmap = {
 24	.reg_bits =		32,
 25	.reg_stride =		4,
 26	.val_bits =		32,
 27	.reg_format_endian =	REGMAP_ENDIAN_BIG,
 28	.val_format_endian =	REGMAP_ENDIAN_BIG,
 29};
 30EXPORT_SYMBOL_GPL(cs40l50_regmap);
 31
 32static const char * const cs40l50_supplies[] = {
 33	"vdd-io",
 34};
 35
 36static const struct regmap_irq cs40l50_reg_irqs[] = {
 37	REGMAP_IRQ_REG(CS40L50_DSP_QUEUE_IRQ, CS40L50_IRQ1_INT_2_OFFSET,
 38		       CS40L50_DSP_QUEUE_MASK),
 39	REGMAP_IRQ_REG(CS40L50_AMP_SHORT_IRQ, CS40L50_IRQ1_INT_1_OFFSET,
 40		       CS40L50_AMP_SHORT_MASK),
 41	REGMAP_IRQ_REG(CS40L50_TEMP_ERR_IRQ, CS40L50_IRQ1_INT_8_OFFSET,
 42		       CS40L50_TEMP_ERR_MASK),
 43	REGMAP_IRQ_REG(CS40L50_BST_UVP_IRQ, CS40L50_IRQ1_INT_9_OFFSET,
 44		       CS40L50_BST_UVP_MASK),
 45	REGMAP_IRQ_REG(CS40L50_BST_SHORT_IRQ, CS40L50_IRQ1_INT_9_OFFSET,
 46		       CS40L50_BST_SHORT_MASK),
 47	REGMAP_IRQ_REG(CS40L50_BST_ILIMIT_IRQ, CS40L50_IRQ1_INT_9_OFFSET,
 48		       CS40L50_BST_ILIMIT_MASK),
 49	REGMAP_IRQ_REG(CS40L50_UVLO_VDDBATT_IRQ, CS40L50_IRQ1_INT_10_OFFSET,
 50		       CS40L50_UVLO_VDDBATT_MASK),
 51	REGMAP_IRQ_REG(CS40L50_GLOBAL_ERROR_IRQ, CS40L50_IRQ1_INT_18_OFFSET,
 52		       CS40L50_GLOBAL_ERROR_MASK),
 53};
 54
 55static struct regmap_irq_chip cs40l50_irq_chip = {
 56	.name =		"cs40l50",
 57	.status_base =	CS40L50_IRQ1_INT_1,
 58	.mask_base =	CS40L50_IRQ1_MASK_1,
 59	.ack_base =	CS40L50_IRQ1_INT_1,
 60	.num_regs =	22,
 61	.irqs =		cs40l50_reg_irqs,
 62	.num_irqs =	ARRAY_SIZE(cs40l50_reg_irqs),
 63	.runtime_pm =	true,
 64};
 65
 66int cs40l50_dsp_write(struct device *dev, struct regmap *regmap, u32 val)
 67{
 68	int i, ret;
 69	u32 ack;
 70
 71	/* Device NAKs if hibernating, so optionally retry */
 72	for (i = 0; i < CS40L50_DSP_TIMEOUT_COUNT; i++) {
 73		ret = regmap_write(regmap, CS40L50_DSP_QUEUE, val);
 74		if (!ret)
 75			break;
 76
 77		usleep_range(CS40L50_DSP_POLL_US, CS40L50_DSP_POLL_US + 100);
 78	}
 79
 80	/* If the write never took place, no need to check for the ACK */
 81	if (i == CS40L50_DSP_TIMEOUT_COUNT) {
 82		dev_err(dev, "Timed out writing %#X to DSP: %d\n", val, ret);
 83		return ret;
 84	}
 85
 86	ret = regmap_read_poll_timeout(regmap, CS40L50_DSP_QUEUE, ack, !ack,
 87				       CS40L50_DSP_POLL_US,
 88				       CS40L50_DSP_POLL_US * CS40L50_DSP_TIMEOUT_COUNT);
 89	if (ret)
 90		dev_err(dev, "DSP failed to ACK %#X: %d\n", val, ret);
 91
 92	return ret;
 93}
 94EXPORT_SYMBOL_GPL(cs40l50_dsp_write);
 95
 96static const struct cs_dsp_region cs40l50_dsp_regions[] = {
 97	{ .type = WMFW_HALO_PM_PACKED, .base = CS40L50_PMEM_0 },
 98	{ .type = WMFW_HALO_XM_PACKED, .base = CS40L50_XMEM_PACKED_0 },
 99	{ .type = WMFW_HALO_YM_PACKED, .base = CS40L50_YMEM_PACKED_0 },
100	{ .type = WMFW_ADSP2_XM, .base = CS40L50_XMEM_UNPACKED24_0 },
101	{ .type = WMFW_ADSP2_YM, .base = CS40L50_YMEM_UNPACKED24_0 },
102};
103
104static const struct reg_sequence cs40l50_internal_vamp_config[] = {
105	{ CS40L50_BST_LPMODE_SEL, CS40L50_DCM_LOW_POWER },
106	{ CS40L50_BLOCK_ENABLES2, CS40L50_OVERTEMP_WARN },
107};
108
109static const struct reg_sequence cs40l50_irq_mask_override[] = {
110	{ CS40L50_IRQ1_MASK_2, CS40L50_IRQ_MASK_2_OVERRIDE },
111	{ CS40L50_IRQ1_MASK_20, CS40L50_IRQ_MASK_20_OVERRIDE },
112};
113
114static int cs40l50_wseq_init(struct cs40l50 *cs40l50)
115{
116	struct cs_dsp *dsp = &cs40l50->dsp;
117
118	cs40l50->wseqs[CS40L50_STANDBY].ctl = cs_dsp_get_ctl(dsp, "STANDBY_SEQUENCE",
119							     WMFW_ADSP2_XM,
120							     CS40L50_PM_ALGO);
121	if (!cs40l50->wseqs[CS40L50_STANDBY].ctl) {
122		dev_err(cs40l50->dev, "Control not found for standby sequence\n");
123		return -ENOENT;
124	}
125
126	cs40l50->wseqs[CS40L50_ACTIVE].ctl = cs_dsp_get_ctl(dsp, "ACTIVE_SEQUENCE",
127							    WMFW_ADSP2_XM,
128							    CS40L50_PM_ALGO);
129	if (!cs40l50->wseqs[CS40L50_ACTIVE].ctl) {
130		dev_err(cs40l50->dev, "Control not found for active sequence\n");
131		return -ENOENT;
132	}
133
134	cs40l50->wseqs[CS40L50_PWR_ON].ctl = cs_dsp_get_ctl(dsp, "PM_PWR_ON_SEQ",
135							    WMFW_ADSP2_XM,
136							    CS40L50_PM_ALGO);
137	if (!cs40l50->wseqs[CS40L50_PWR_ON].ctl) {
138		dev_err(cs40l50->dev, "Control not found for power-on sequence\n");
139		return -ENOENT;
140	}
141
142	return cs_dsp_wseq_init(&cs40l50->dsp, cs40l50->wseqs, ARRAY_SIZE(cs40l50->wseqs));
143}
144
145static int cs40l50_dsp_config(struct cs40l50 *cs40l50)
146{
147	int ret;
148
149	/* Configure internal V_AMP supply */
150	ret = regmap_multi_reg_write(cs40l50->regmap, cs40l50_internal_vamp_config,
151				     ARRAY_SIZE(cs40l50_internal_vamp_config));
152	if (ret)
153		return ret;
154
155	ret = cs_dsp_wseq_multi_write(&cs40l50->dsp, &cs40l50->wseqs[CS40L50_PWR_ON],
156				      cs40l50_internal_vamp_config, CS_DSP_WSEQ_FULL,
157				      ARRAY_SIZE(cs40l50_internal_vamp_config), false);
158	if (ret)
159		return ret;
160
161	/* Override firmware defaults for IRQ masks */
162	ret = regmap_multi_reg_write(cs40l50->regmap, cs40l50_irq_mask_override,
163				     ARRAY_SIZE(cs40l50_irq_mask_override));
164	if (ret)
165		return ret;
166
167	return cs_dsp_wseq_multi_write(&cs40l50->dsp, &cs40l50->wseqs[CS40L50_PWR_ON],
168				       cs40l50_irq_mask_override, CS_DSP_WSEQ_FULL,
169				       ARRAY_SIZE(cs40l50_irq_mask_override), false);
170}
171
172static int cs40l50_dsp_post_run(struct cs_dsp *dsp)
173{
174	struct cs40l50 *cs40l50 = container_of(dsp, struct cs40l50, dsp);
175	int ret;
176
177	ret = cs40l50_wseq_init(cs40l50);
178	if (ret)
179		return ret;
180
181	ret = cs40l50_dsp_config(cs40l50);
182	if (ret) {
183		dev_err(cs40l50->dev, "Failed to configure DSP: %d\n", ret);
184		return ret;
185	}
186
187	ret = devm_mfd_add_devices(cs40l50->dev, PLATFORM_DEVID_NONE, cs40l50_devs,
188				   ARRAY_SIZE(cs40l50_devs), NULL, 0, NULL);
189	if (ret)
190		dev_err(cs40l50->dev, "Failed to add child devices: %d\n", ret);
191
192	return ret;
193}
194
195static const struct cs_dsp_client_ops client_ops = {
196	.post_run = cs40l50_dsp_post_run,
197};
198
199static void cs40l50_dsp_remove(void *data)
200{
201	cs_dsp_remove(data);
202}
203
204static int cs40l50_dsp_init(struct cs40l50 *cs40l50)
205{
206	int ret;
207
208	cs40l50->dsp.num = 1;
209	cs40l50->dsp.type = WMFW_HALO;
210	cs40l50->dsp.dev = cs40l50->dev;
211	cs40l50->dsp.regmap = cs40l50->regmap;
212	cs40l50->dsp.base = CS40L50_CORE_BASE;
213	cs40l50->dsp.base_sysinfo = CS40L50_SYS_INFO_ID;
214	cs40l50->dsp.mem = cs40l50_dsp_regions;
215	cs40l50->dsp.num_mems = ARRAY_SIZE(cs40l50_dsp_regions);
216	cs40l50->dsp.no_core_startstop = true;
217	cs40l50->dsp.client_ops = &client_ops;
218
219	ret = cs_dsp_halo_init(&cs40l50->dsp);
220	if (ret)
221		return ret;
222
223	return devm_add_action_or_reset(cs40l50->dev, cs40l50_dsp_remove,
224					&cs40l50->dsp);
225}
226
227static int cs40l50_reset_dsp(struct cs40l50 *cs40l50)
228{
229	int ret;
230
231	mutex_lock(&cs40l50->lock);
232
233	if (cs40l50->dsp.running)
234		cs_dsp_stop(&cs40l50->dsp);
235
236	if (cs40l50->dsp.booted)
237		cs_dsp_power_down(&cs40l50->dsp);
238
239	ret = cs40l50_dsp_write(cs40l50->dev, cs40l50->regmap, CS40L50_SHUTDOWN);
240	if (ret)
241		goto err_mutex;
242
243	ret = cs_dsp_power_up(&cs40l50->dsp, cs40l50->fw, "cs40l50.wmfw",
244			      cs40l50->bin, "cs40l50.bin", "cs40l50");
245	if (ret)
246		goto err_mutex;
247
248	ret = cs40l50_dsp_write(cs40l50->dev, cs40l50->regmap, CS40L50_SYSTEM_RESET);
249	if (ret)
250		goto err_mutex;
251
252	ret = cs40l50_dsp_write(cs40l50->dev, cs40l50->regmap, CS40L50_PREVENT_HIBER);
253	if (ret)
254		goto err_mutex;
255
256	ret = cs_dsp_run(&cs40l50->dsp);
257err_mutex:
258	mutex_unlock(&cs40l50->lock);
259
260	return ret;
261}
262
263static void cs40l50_dsp_power_down(void *data)
264{
265	cs_dsp_power_down(data);
266}
267
268static void cs40l50_dsp_stop(void *data)
269{
270	cs_dsp_stop(data);
271}
272
273static void cs40l50_dsp_bringup(const struct firmware *bin, void *context)
274{
275	struct cs40l50 *cs40l50 = context;
276	u32 nwaves;
277	int ret;
278
279	/* Wavetable is optional; bringup DSP regardless */
280	cs40l50->bin = bin;
281
282	ret = cs40l50_reset_dsp(cs40l50);
283	if (ret) {
284		dev_err(cs40l50->dev, "Failed to reset DSP: %d\n", ret);
285		goto err_fw;
286	}
287
288	ret = regmap_read(cs40l50->regmap, CS40L50_NUM_WAVES, &nwaves);
289	if (ret)
290		goto err_fw;
291
292	dev_info(cs40l50->dev, "%u RAM effects loaded\n", nwaves);
293
294	/* Add teardown actions for first-time bringup */
295	ret = devm_add_action_or_reset(cs40l50->dev, cs40l50_dsp_power_down,
296				       &cs40l50->dsp);
297	if (ret) {
298		dev_err(cs40l50->dev, "Failed to add power down action: %d\n", ret);
299		goto err_fw;
300	}
301
302	ret = devm_add_action_or_reset(cs40l50->dev, cs40l50_dsp_stop, &cs40l50->dsp);
303	if (ret)
304		dev_err(cs40l50->dev, "Failed to add stop action: %d\n", ret);
305err_fw:
306	release_firmware(cs40l50->bin);
307	release_firmware(cs40l50->fw);
308}
309
310static void cs40l50_request_firmware(const struct firmware *fw, void *context)
311{
312	struct cs40l50 *cs40l50 = context;
313	int ret;
314
315	if (!fw) {
316		dev_err(cs40l50->dev, "No firmware file found\n");
317		return;
318	}
319
320	cs40l50->fw = fw;
321
322	ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, CS40L50_WT,
323				      cs40l50->dev, GFP_KERNEL, cs40l50,
324				      cs40l50_dsp_bringup);
325	if (ret) {
326		dev_err(cs40l50->dev, "Failed to request %s: %d\n", CS40L50_WT, ret);
327		release_firmware(cs40l50->fw);
328	}
329}
330
331struct cs40l50_irq {
332	const char *name;
333	int virq;
334};
335
336static struct cs40l50_irq cs40l50_irqs[] = {
337	{ "DSP", },
338	{ "Global", },
339	{ "Boost UVLO", },
340	{ "Boost current limit", },
341	{ "Boost short", },
342	{ "Boost undervolt", },
343	{ "Overtemp", },
344	{ "Amp short", },
345};
346
347static const struct reg_sequence cs40l50_err_rls[] = {
348	{ CS40L50_ERR_RLS, CS40L50_GLOBAL_ERR_RLS_SET },
349	{ CS40L50_ERR_RLS, CS40L50_GLOBAL_ERR_RLS_CLEAR },
350};
351
352static irqreturn_t cs40l50_hw_err(int irq, void *data)
353{
354	struct cs40l50 *cs40l50 = data;
355	int ret = 0, i;
356
357	mutex_lock(&cs40l50->lock);
358
359	/* Log hardware interrupt and execute error release sequence */
360	for (i = 1; i < ARRAY_SIZE(cs40l50_irqs); i++) {
361		if (cs40l50_irqs[i].virq == irq) {
362			dev_err(cs40l50->dev, "%s error\n", cs40l50_irqs[i].name);
363			ret = regmap_multi_reg_write(cs40l50->regmap, cs40l50_err_rls,
364						     ARRAY_SIZE(cs40l50_err_rls));
365			break;
366		}
367	}
368
369	mutex_unlock(&cs40l50->lock);
370	return IRQ_RETVAL(!ret);
371}
372
373static irqreturn_t cs40l50_dsp_queue(int irq, void *data)
374{
375	struct cs40l50 *cs40l50 = data;
376	u32 rd_ptr, val, wt_ptr;
377	int ret = 0;
378
379	mutex_lock(&cs40l50->lock);
380
381	/* Read from DSP queue, log, and update read pointer */
382	while (!ret) {
383		ret = regmap_read(cs40l50->regmap, CS40L50_DSP_QUEUE_WT, &wt_ptr);
384		if (ret)
385			break;
386
387		ret = regmap_read(cs40l50->regmap, CS40L50_DSP_QUEUE_RD, &rd_ptr);
388		if (ret)
389			break;
390
391		/* Check if queue is empty */
392		if (wt_ptr == rd_ptr)
393			break;
394
395		ret = regmap_read(cs40l50->regmap, rd_ptr, &val);
396		if (ret)
397			break;
398
399		dev_dbg(cs40l50->dev, "DSP payload: %#X", val);
400
401		rd_ptr += sizeof(u32);
402
403		if (rd_ptr > CS40L50_DSP_QUEUE_END)
404			rd_ptr = CS40L50_DSP_QUEUE_BASE;
405
406		ret = regmap_write(cs40l50->regmap, CS40L50_DSP_QUEUE_RD, rd_ptr);
407	}
408
409	mutex_unlock(&cs40l50->lock);
410
411	return IRQ_RETVAL(!ret);
412}
413
414static int cs40l50_irq_init(struct cs40l50 *cs40l50)
415{
416	int ret, i, virq;
417
418	ret = devm_regmap_add_irq_chip(cs40l50->dev, cs40l50->regmap, cs40l50->irq,
419				       IRQF_ONESHOT | IRQF_SHARED, 0,
420				       &cs40l50_irq_chip, &cs40l50->irq_data);
421	if (ret) {
422		dev_err(cs40l50->dev, "Failed adding IRQ chip\n");
423		return ret;
424	}
425
426	for (i = 0; i < ARRAY_SIZE(cs40l50_irqs); i++) {
427		virq = regmap_irq_get_virq(cs40l50->irq_data, i);
428		if (virq < 0) {
429			dev_err(cs40l50->dev, "Failed getting virq for %s\n",
430				cs40l50_irqs[i].name);
431			return virq;
432		}
433
434		cs40l50_irqs[i].virq = virq;
435
436		/* Handle DSP and hardware interrupts separately */
437		ret = devm_request_threaded_irq(cs40l50->dev, virq, NULL,
438						i ? cs40l50_hw_err : cs40l50_dsp_queue,
439						IRQF_ONESHOT | IRQF_SHARED,
440						cs40l50_irqs[i].name, cs40l50);
441		if (ret) {
442			return dev_err_probe(cs40l50->dev, ret,
443					     "Failed requesting %s IRQ\n",
444					     cs40l50_irqs[i].name);
445		}
446	}
447
448	return 0;
449}
450
451static int cs40l50_get_model(struct cs40l50 *cs40l50)
452{
453	int ret;
454
455	ret = regmap_read(cs40l50->regmap, CS40L50_DEVID, &cs40l50->devid);
456	if (ret)
457		return ret;
458
459	if (cs40l50->devid != CS40L50_DEVID_A)
460		return -EINVAL;
461
462	ret = regmap_read(cs40l50->regmap, CS40L50_REVID, &cs40l50->revid);
463	if (ret)
464		return ret;
465
466	if (cs40l50->revid < CS40L50_REVID_B0)
467		return -EINVAL;
468
469	dev_dbg(cs40l50->dev, "Cirrus Logic CS40L50 rev. %02X\n", cs40l50->revid);
470
471	return 0;
472}
473
474static int cs40l50_pm_runtime_setup(struct device *dev)
475{
476	int ret;
477
478	pm_runtime_set_autosuspend_delay(dev, CS40L50_AUTOSUSPEND_MS);
479	pm_runtime_use_autosuspend(dev);
480	pm_runtime_get_noresume(dev);
481	ret = pm_runtime_set_active(dev);
482	if (ret)
483		return ret;
484
485	return devm_pm_runtime_enable(dev);
486}
487
488int cs40l50_probe(struct cs40l50 *cs40l50)
489{
490	struct device *dev = cs40l50->dev;
491	int ret;
492
493	mutex_init(&cs40l50->lock);
494
495	cs40l50->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
496	if (IS_ERR(cs40l50->reset_gpio))
497		return dev_err_probe(dev, PTR_ERR(cs40l50->reset_gpio),
498				     "Failed getting reset GPIO\n");
499
500	ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(cs40l50_supplies),
501					     cs40l50_supplies);
502	if (ret)
503		return dev_err_probe(dev, ret, "Failed getting supplies\n");
504
505	/* Ensure minimum reset pulse width */
506	usleep_range(CS40L50_RESET_PULSE_US, CS40L50_RESET_PULSE_US + 100);
507
508	gpiod_set_value_cansleep(cs40l50->reset_gpio, 0);
509
510	/* Wait for control port to be ready */
511	usleep_range(CS40L50_CP_READY_US, CS40L50_CP_READY_US + 100);
512
513	ret = cs40l50_get_model(cs40l50);
514	if (ret)
515		return dev_err_probe(dev, ret, "Failed to get part number\n");
516
517	ret = cs40l50_dsp_init(cs40l50);
518	if (ret)
519		return dev_err_probe(dev, ret, "Failed to initialize DSP\n");
520
521	ret = cs40l50_pm_runtime_setup(dev);
522	if (ret)
523		return dev_err_probe(dev, ret, "Failed to initialize runtime PM\n");
524
525	ret = cs40l50_irq_init(cs40l50);
526	if (ret)
527		return ret;
528
529	ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, CS40L50_FW,
530				      dev, GFP_KERNEL, cs40l50, cs40l50_request_firmware);
531	if (ret)
532		return dev_err_probe(dev, ret, "Failed to request %s\n", CS40L50_FW);
533
534	pm_runtime_mark_last_busy(dev);
535	pm_runtime_put_autosuspend(dev);
536
537	return 0;
538}
539EXPORT_SYMBOL_GPL(cs40l50_probe);
540
541int cs40l50_remove(struct cs40l50 *cs40l50)
542{
543	gpiod_set_value_cansleep(cs40l50->reset_gpio, 1);
544
545	return 0;
546}
547EXPORT_SYMBOL_GPL(cs40l50_remove);
548
549static int cs40l50_runtime_suspend(struct device *dev)
550{
551	struct cs40l50 *cs40l50 = dev_get_drvdata(dev);
552
553	return regmap_write(cs40l50->regmap, CS40L50_DSP_QUEUE, CS40L50_ALLOW_HIBER);
554}
555
556static int cs40l50_runtime_resume(struct device *dev)
557{
558	struct cs40l50 *cs40l50 = dev_get_drvdata(dev);
559
560	return cs40l50_dsp_write(dev, cs40l50->regmap, CS40L50_PREVENT_HIBER);
561}
562
563EXPORT_GPL_DEV_PM_OPS(cs40l50_pm_ops) = {
564	RUNTIME_PM_OPS(cs40l50_runtime_suspend, cs40l50_runtime_resume, NULL)
565};
566
567MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver");
568MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
569MODULE_LICENSE("GPL");
570MODULE_IMPORT_NS("FW_CS_DSP");