Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mfd: cs40l50: Add support for CS40L50 core driver

Introduce support for Cirrus Logic Device CS40L50: a
haptic driver with waveform memory, integrated DSP,
and closed-loop algorithms.

The MFD component registers and initializes the device.

Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
Reviewed-by: Jeff LaBundy <jeff@labundy.com>
Link: https://lore.kernel.org/r/20240620161745.2312359-4-jogletre@opensource.cirrus.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

James Ogletree and committed by
Lee Jones
cb626376 2fab5aba

+879
+2
MAINTAINERS
··· 5213 5213 L: patches@opensource.cirrus.com 5214 5214 S: Supported 5215 5215 F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml 5216 + F: drivers/mfd/cs40l* 5217 + F: include/linux/mfd/cs40l* 5216 5218 5217 5219 CIRRUS LOGIC DSP FIRMWARE DRIVER 5218 5220 M: Simon Trimmer <simont@opensource.cirrus.com>
+30
drivers/mfd/Kconfig
··· 2243 2243 2244 2244 endmenu 2245 2245 2246 + config MFD_CS40L50_CORE 2247 + tristate 2248 + select MFD_CORE 2249 + select FW_CS_DSP 2250 + select REGMAP_IRQ 2251 + 2252 + config MFD_CS40L50_I2C 2253 + tristate "Cirrus Logic CS40L50 (I2C)" 2254 + select REGMAP_I2C 2255 + select MFD_CS40L50_CORE 2256 + depends on I2C 2257 + help 2258 + Select this to support the Cirrus Logic CS40L50 Haptic 2259 + Driver over I2C. 2260 + 2261 + This driver can be built as a module. If built as a module it will be 2262 + called "cs40l50-i2c". 2263 + 2264 + config MFD_CS40L50_SPI 2265 + tristate "Cirrus Logic CS40L50 (SPI)" 2266 + select REGMAP_SPI 2267 + select MFD_CS40L50_CORE 2268 + depends on SPI 2269 + help 2270 + Select this to support the Cirrus Logic CS40L50 Haptic 2271 + Driver over SPI. 2272 + 2273 + This driver can be built as a module. If built as a module it will be 2274 + called "cs40l50-spi". 2275 + 2246 2276 config MFD_VEXPRESS_SYSREG 2247 2277 tristate "Versatile Express System Registers" 2248 2278 depends on VEXPRESS_CONFIG && GPIOLIB
+4
drivers/mfd/Makefile
··· 88 88 obj-$(CONFIG_MFD_MADERA_I2C) += madera-i2c.o 89 89 obj-$(CONFIG_MFD_MADERA_SPI) += madera-spi.o 90 90 91 + obj-$(CONFIG_MFD_CS40L50_CORE) += cs40l50-core.o 92 + obj-$(CONFIG_MFD_CS40L50_I2C) += cs40l50-i2c.o 93 + obj-$(CONFIG_MFD_CS40L50_SPI) += cs40l50-spi.o 94 + 91 95 obj-$(CONFIG_TPS6105X) += tps6105x.o 92 96 obj-$(CONFIG_TPS65010) += tps65010.o 93 97 obj-$(CONFIG_TPS6507X) += tps6507x.o
+570
drivers/mfd/cs40l50-core.c
··· 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 + 18 + static const struct mfd_cell cs40l50_devs[] = { 19 + { .name = "cs40l50-codec", }, 20 + { .name = "cs40l50-vibra", }, 21 + }; 22 + 23 + const 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 + }; 30 + EXPORT_SYMBOL_GPL(cs40l50_regmap); 31 + 32 + static const char * const cs40l50_supplies[] = { 33 + "vdd-io", 34 + }; 35 + 36 + static 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 + 55 + static 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 + 66 + int 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 + } 94 + EXPORT_SYMBOL_GPL(cs40l50_dsp_write); 95 + 96 + static 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 + 104 + static 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 + 109 + static 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 + 114 + static 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 + 145 + static 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 + 172 + static 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 + 195 + static const struct cs_dsp_client_ops client_ops = { 196 + .post_run = cs40l50_dsp_post_run, 197 + }; 198 + 199 + static void cs40l50_dsp_remove(void *data) 200 + { 201 + cs_dsp_remove(data); 202 + } 203 + 204 + static 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 + 227 + static 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); 257 + err_mutex: 258 + mutex_unlock(&cs40l50->lock); 259 + 260 + return ret; 261 + } 262 + 263 + static void cs40l50_dsp_power_down(void *data) 264 + { 265 + cs_dsp_power_down(data); 266 + } 267 + 268 + static void cs40l50_dsp_stop(void *data) 269 + { 270 + cs_dsp_stop(data); 271 + } 272 + 273 + static 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); 305 + err_fw: 306 + release_firmware(cs40l50->bin); 307 + release_firmware(cs40l50->fw); 308 + } 309 + 310 + static 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 + 331 + struct cs40l50_irq { 332 + const char *name; 333 + int virq; 334 + }; 335 + 336 + static 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 + 347 + static 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 + 352 + static 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 + 373 + static 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 + 414 + static 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 + 451 + static 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 + 474 + static 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 + 488 + int 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 + } 539 + EXPORT_SYMBOL_GPL(cs40l50_probe); 540 + 541 + int cs40l50_remove(struct cs40l50 *cs40l50) 542 + { 543 + gpiod_set_value_cansleep(cs40l50->reset_gpio, 1); 544 + 545 + return 0; 546 + } 547 + EXPORT_SYMBOL_GPL(cs40l50_remove); 548 + 549 + static 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 + 556 + static 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 + 563 + EXPORT_GPL_DEV_PM_OPS(cs40l50_pm_ops) = { 564 + RUNTIME_PM_OPS(cs40l50_runtime_suspend, cs40l50_runtime_resume, NULL) 565 + }; 566 + 567 + MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver"); 568 + MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>"); 569 + MODULE_LICENSE("GPL"); 570 + MODULE_IMPORT_NS(FW_CS_DSP);
+68
drivers/mfd/cs40l50-i2c.c
··· 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/i2c.h> 12 + #include <linux/mfd/cs40l50.h> 13 + 14 + static int cs40l50_i2c_probe(struct i2c_client *i2c) 15 + { 16 + struct cs40l50 *cs40l50; 17 + 18 + cs40l50 = devm_kzalloc(&i2c->dev, sizeof(*cs40l50), GFP_KERNEL); 19 + if (!cs40l50) 20 + return -ENOMEM; 21 + 22 + i2c_set_clientdata(i2c, cs40l50); 23 + 24 + cs40l50->dev = &i2c->dev; 25 + cs40l50->irq = i2c->irq; 26 + 27 + cs40l50->regmap = devm_regmap_init_i2c(i2c, &cs40l50_regmap); 28 + if (IS_ERR(cs40l50->regmap)) 29 + return dev_err_probe(cs40l50->dev, PTR_ERR(cs40l50->regmap), 30 + "Failed to initialize register map\n"); 31 + 32 + return cs40l50_probe(cs40l50); 33 + } 34 + 35 + static void cs40l50_i2c_remove(struct i2c_client *i2c) 36 + { 37 + struct cs40l50 *cs40l50 = i2c_get_clientdata(i2c); 38 + 39 + cs40l50_remove(cs40l50); 40 + } 41 + 42 + static const struct i2c_device_id cs40l50_id_i2c[] = { 43 + { "cs40l50" }, 44 + {} 45 + }; 46 + MODULE_DEVICE_TABLE(i2c, cs40l50_id_i2c); 47 + 48 + static const struct of_device_id cs40l50_of_match[] = { 49 + { .compatible = "cirrus,cs40l50" }, 50 + {} 51 + }; 52 + MODULE_DEVICE_TABLE(of, cs40l50_of_match); 53 + 54 + static struct i2c_driver cs40l50_i2c_driver = { 55 + .driver = { 56 + .name = "cs40l50", 57 + .of_match_table = cs40l50_of_match, 58 + .pm = pm_ptr(&cs40l50_pm_ops), 59 + }, 60 + .id_table = cs40l50_id_i2c, 61 + .probe = cs40l50_i2c_probe, 62 + .remove = cs40l50_i2c_remove, 63 + }; 64 + module_i2c_driver(cs40l50_i2c_driver); 65 + 66 + MODULE_DESCRIPTION("CS40L50 I2C Driver"); 67 + MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>"); 68 + MODULE_LICENSE("GPL");
+68
drivers/mfd/cs40l50-spi.c
··· 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/mfd/cs40l50.h> 12 + #include <linux/spi/spi.h> 13 + 14 + static int cs40l50_spi_probe(struct spi_device *spi) 15 + { 16 + struct cs40l50 *cs40l50; 17 + 18 + cs40l50 = devm_kzalloc(&spi->dev, sizeof(*cs40l50), GFP_KERNEL); 19 + if (!cs40l50) 20 + return -ENOMEM; 21 + 22 + spi_set_drvdata(spi, cs40l50); 23 + 24 + cs40l50->dev = &spi->dev; 25 + cs40l50->irq = spi->irq; 26 + 27 + cs40l50->regmap = devm_regmap_init_spi(spi, &cs40l50_regmap); 28 + if (IS_ERR(cs40l50->regmap)) 29 + return dev_err_probe(cs40l50->dev, PTR_ERR(cs40l50->regmap), 30 + "Failed to initialize register map\n"); 31 + 32 + return cs40l50_probe(cs40l50); 33 + } 34 + 35 + static void cs40l50_spi_remove(struct spi_device *spi) 36 + { 37 + struct cs40l50 *cs40l50 = spi_get_drvdata(spi); 38 + 39 + cs40l50_remove(cs40l50); 40 + } 41 + 42 + static const struct spi_device_id cs40l50_id_spi[] = { 43 + { "cs40l50" }, 44 + {} 45 + }; 46 + MODULE_DEVICE_TABLE(spi, cs40l50_id_spi); 47 + 48 + static const struct of_device_id cs40l50_of_match[] = { 49 + { .compatible = "cirrus,cs40l50" }, 50 + {} 51 + }; 52 + MODULE_DEVICE_TABLE(of, cs40l50_of_match); 53 + 54 + static struct spi_driver cs40l50_spi_driver = { 55 + .driver = { 56 + .name = "cs40l50", 57 + .of_match_table = cs40l50_of_match, 58 + .pm = pm_ptr(&cs40l50_pm_ops), 59 + }, 60 + .id_table = cs40l50_id_spi, 61 + .probe = cs40l50_spi_probe, 62 + .remove = cs40l50_spi_remove, 63 + }; 64 + module_spi_driver(cs40l50_spi_driver); 65 + 66 + MODULE_DESCRIPTION("CS40L50 SPI Driver"); 67 + MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>"); 68 + MODULE_LICENSE("GPL");
+137
include/linux/mfd/cs40l50.h
··· 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 + #ifndef __MFD_CS40L50_H__ 12 + #define __MFD_CS40L50_H__ 13 + 14 + #include <linux/firmware/cirrus/cs_dsp.h> 15 + #include <linux/gpio/consumer.h> 16 + #include <linux/pm.h> 17 + #include <linux/regmap.h> 18 + 19 + /* Power Supply Configuration */ 20 + #define CS40L50_BLOCK_ENABLES2 0x201C 21 + #define CS40L50_ERR_RLS 0x2034 22 + #define CS40L50_BST_LPMODE_SEL 0x3810 23 + #define CS40L50_DCM_LOW_POWER 0x1 24 + #define CS40L50_OVERTEMP_WARN 0x4000010 25 + 26 + /* Interrupts */ 27 + #define CS40L50_IRQ1_INT_1 0xE010 28 + #define CS40L50_IRQ1_BASE CS40L50_IRQ1_INT_1 29 + #define CS40L50_IRQ1_INT_2 0xE014 30 + #define CS40L50_IRQ1_INT_8 0xE02C 31 + #define CS40L50_IRQ1_INT_9 0xE030 32 + #define CS40L50_IRQ1_INT_10 0xE034 33 + #define CS40L50_IRQ1_INT_18 0xE054 34 + #define CS40L50_IRQ1_MASK_1 0xE090 35 + #define CS40L50_IRQ1_MASK_2 0xE094 36 + #define CS40L50_IRQ1_MASK_20 0xE0DC 37 + #define CS40L50_IRQ1_INT_1_OFFSET (CS40L50_IRQ1_INT_1 - CS40L50_IRQ1_BASE) 38 + #define CS40L50_IRQ1_INT_2_OFFSET (CS40L50_IRQ1_INT_2 - CS40L50_IRQ1_BASE) 39 + #define CS40L50_IRQ1_INT_8_OFFSET (CS40L50_IRQ1_INT_8 - CS40L50_IRQ1_BASE) 40 + #define CS40L50_IRQ1_INT_9_OFFSET (CS40L50_IRQ1_INT_9 - CS40L50_IRQ1_BASE) 41 + #define CS40L50_IRQ1_INT_10_OFFSET (CS40L50_IRQ1_INT_10 - CS40L50_IRQ1_BASE) 42 + #define CS40L50_IRQ1_INT_18_OFFSET (CS40L50_IRQ1_INT_18 - CS40L50_IRQ1_BASE) 43 + #define CS40L50_IRQ_MASK_2_OVERRIDE 0xFFDF7FFF 44 + #define CS40L50_IRQ_MASK_20_OVERRIDE 0x15C01000 45 + #define CS40L50_AMP_SHORT_MASK BIT(31) 46 + #define CS40L50_DSP_QUEUE_MASK BIT(21) 47 + #define CS40L50_TEMP_ERR_MASK BIT(31) 48 + #define CS40L50_BST_UVP_MASK BIT(6) 49 + #define CS40L50_BST_SHORT_MASK BIT(7) 50 + #define CS40L50_BST_ILIMIT_MASK BIT(18) 51 + #define CS40L50_UVLO_VDDBATT_MASK BIT(16) 52 + #define CS40L50_GLOBAL_ERROR_MASK BIT(15) 53 + 54 + enum cs40l50_irq_list { 55 + CS40L50_DSP_QUEUE_IRQ, 56 + CS40L50_GLOBAL_ERROR_IRQ, 57 + CS40L50_UVLO_VDDBATT_IRQ, 58 + CS40L50_BST_ILIMIT_IRQ, 59 + CS40L50_BST_SHORT_IRQ, 60 + CS40L50_BST_UVP_IRQ, 61 + CS40L50_TEMP_ERR_IRQ, 62 + CS40L50_AMP_SHORT_IRQ, 63 + }; 64 + 65 + /* DSP */ 66 + #define CS40L50_XMEM_PACKED_0 0x2000000 67 + #define CS40L50_XMEM_UNPACKED24_0 0x2800000 68 + #define CS40L50_SYS_INFO_ID 0x25E0000 69 + #define CS40L50_DSP_QUEUE_WT 0x28042C8 70 + #define CS40L50_DSP_QUEUE_RD 0x28042CC 71 + #define CS40L50_NUM_WAVES 0x2805C18 72 + #define CS40L50_CORE_BASE 0x2B80000 73 + #define CS40L50_YMEM_PACKED_0 0x2C00000 74 + #define CS40L50_YMEM_UNPACKED24_0 0x3400000 75 + #define CS40L50_PMEM_0 0x3800000 76 + #define CS40L50_DSP_POLL_US 1000 77 + #define CS40L50_DSP_TIMEOUT_COUNT 100 78 + #define CS40L50_RESET_PULSE_US 2200 79 + #define CS40L50_CP_READY_US 3100 80 + #define CS40L50_AUTOSUSPEND_MS 2000 81 + #define CS40L50_PM_ALGO 0x9F206 82 + #define CS40L50_GLOBAL_ERR_RLS_SET BIT(11) 83 + #define CS40L50_GLOBAL_ERR_RLS_CLEAR 0 84 + 85 + enum cs40l50_wseqs { 86 + CS40L50_PWR_ON, 87 + CS40L50_STANDBY, 88 + CS40L50_ACTIVE, 89 + CS40L50_NUM_WSEQS, 90 + }; 91 + 92 + /* DSP Queue */ 93 + #define CS40L50_DSP_QUEUE_BASE 0x11004 94 + #define CS40L50_DSP_QUEUE_END 0x1101C 95 + #define CS40L50_DSP_QUEUE 0x11020 96 + #define CS40L50_PREVENT_HIBER 0x2000003 97 + #define CS40L50_ALLOW_HIBER 0x2000004 98 + #define CS40L50_SHUTDOWN 0x2000005 99 + #define CS40L50_SYSTEM_RESET 0x2000007 100 + #define CS40L50_START_I2S 0x3000002 101 + #define CS40L50_OWT_PUSH 0x3000008 102 + #define CS40L50_STOP_PLAYBACK 0x5000000 103 + #define CS40L50_OWT_DELETE 0xD000000 104 + 105 + /* Firmware files */ 106 + #define CS40L50_FW "cs40l50.wmfw" 107 + #define CS40L50_WT "cs40l50.bin" 108 + 109 + /* Device */ 110 + #define CS40L50_DEVID 0x0 111 + #define CS40L50_REVID 0x4 112 + #define CS40L50_DEVID_A 0x40A50 113 + #define CS40L50_REVID_B0 0xB0 114 + 115 + struct cs40l50 { 116 + struct device *dev; 117 + struct regmap *regmap; 118 + struct mutex lock; 119 + struct cs_dsp dsp; 120 + struct gpio_desc *reset_gpio; 121 + struct regmap_irq_chip_data *irq_data; 122 + const struct firmware *fw; 123 + const struct firmware *bin; 124 + struct cs_dsp_wseq wseqs[CS40L50_NUM_WSEQS]; 125 + int irq; 126 + u32 devid; 127 + u32 revid; 128 + }; 129 + 130 + int cs40l50_dsp_write(struct device *dev, struct regmap *regmap, u32 val); 131 + int cs40l50_probe(struct cs40l50 *cs40l50); 132 + int cs40l50_remove(struct cs40l50 *cs40l50); 133 + 134 + extern const struct regmap_config cs40l50_regmap; 135 + extern const struct dev_pm_ops cs40l50_pm_ops; 136 + 137 + #endif /* __MFD_CS40L50_H__ */