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