brintos

brintos / linux-shallow public Read only

0
0
Text · 14.7 KiB · 26e7a76 Raw
571 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * CS40L50 Advanced Haptic Driver with waveform memory,4 * integrated DSP, and closed-loop algorithms5 *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);571