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

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'ib-mfd-firmware-input-sound-soc-6.11' into ibs-for-mfd-merged

+2148
+68
Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
··· 1 + # SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause 2 + %YAML 1.2 3 + --- 4 + $id: http://devicetree.org/schemas/input/cirrus,cs40l50.yaml# 5 + $schema: http://devicetree.org/meta-schemas/core.yaml# 6 + 7 + title: Cirrus Logic CS40L50 Advanced Haptic Driver 8 + 9 + maintainers: 10 + - James Ogletree <jogletre@opensource.cirrus.com> 11 + 12 + description: 13 + CS40L50 is a haptic driver with waveform memory, 14 + integrated DSP, and closed-loop algorithms. 15 + 16 + properties: 17 + compatible: 18 + enum: 19 + - cirrus,cs40l50 20 + 21 + reg: 22 + maxItems: 1 23 + 24 + interrupts: 25 + maxItems: 1 26 + 27 + reset-gpios: 28 + maxItems: 1 29 + 30 + vdd-a-supply: 31 + description: Power supply for internal analog circuits. 32 + 33 + vdd-p-supply: 34 + description: Power supply for always-on circuits. 35 + 36 + vdd-io-supply: 37 + description: Power supply for digital input/output. 38 + 39 + vdd-b-supply: 40 + description: Power supply for the boost converter. 41 + 42 + required: 43 + - compatible 44 + - reg 45 + - interrupts 46 + - reset-gpios 47 + - vdd-io-supply 48 + 49 + additionalProperties: false 50 + 51 + examples: 52 + - | 53 + #include <dt-bindings/gpio/gpio.h> 54 + #include <dt-bindings/interrupt-controller/irq.h> 55 + 56 + i2c { 57 + #address-cells = <1>; 58 + #size-cells = <0>; 59 + 60 + haptic-driver@34 { 61 + compatible = "cirrus,cs40l50"; 62 + reg = <0x34>; 63 + interrupt-parent = <&gpio>; 64 + interrupts = <113 IRQ_TYPE_LEVEL_LOW>; 65 + reset-gpios = <&gpio 112 GPIO_ACTIVE_LOW>; 66 + vdd-io-supply = <&vreg>; 67 + }; 68 + };
+12
MAINTAINERS
··· 5211 5211 F: sound/pci/hda/hda_cs_dsp_ctl.* 5212 5212 F: sound/soc/codecs/cs* 5213 5213 5214 + CIRRUS LOGIC HAPTIC DRIVERS 5215 + M: James Ogletree <jogletre@opensource.cirrus.com> 5216 + M: Fred Treven <fred.treven@cirrus.com> 5217 + M: Ben Bright <ben.bright@cirrus.com> 5218 + L: patches@opensource.cirrus.com 5219 + S: Supported 5220 + F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml 5221 + F: drivers/input/misc/cs40l* 5222 + F: drivers/mfd/cs40l* 5223 + F: include/linux/mfd/cs40l* 5224 + F: sound/soc/codecs/cs40l* 5225 + 5214 5226 CIRRUS LOGIC DSP FIRMWARE DRIVER 5215 5227 M: Simon Trimmer <simont@opensource.cirrus.com> 5216 5228 M: Charles Keepax <ckeepax@opensource.cirrus.com>
+278
drivers/firmware/cirrus/cs_dsp.c
··· 275 275 #define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff 276 276 #define HALO_MPU_VIO_ERR_SRC_SHIFT 0 277 277 278 + /* 279 + * Write Sequence 280 + */ 281 + #define WSEQ_OP_MAX_WORDS 3 282 + #define WSEQ_END_OF_SCRIPT 0xFFFFFF 283 + 278 284 struct cs_dsp_ops { 279 285 bool (*validate_version)(struct cs_dsp *dsp, unsigned int version); 280 286 unsigned int (*parse_sizes)(struct cs_dsp *dsp, ··· 3403 3397 return result; 3404 3398 } 3405 3399 EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_read, FW_CS_DSP); 3400 + 3401 + 3402 + struct cs_dsp_wseq_op { 3403 + struct list_head list; 3404 + u32 address; 3405 + u32 data; 3406 + u16 offset; 3407 + u8 operation; 3408 + }; 3409 + 3410 + static void cs_dsp_wseq_clear(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq) 3411 + { 3412 + struct cs_dsp_wseq_op *op, *op_tmp; 3413 + 3414 + list_for_each_entry_safe(op, op_tmp, &wseq->ops, list) { 3415 + list_del(&op->list); 3416 + devm_kfree(dsp->dev, op); 3417 + } 3418 + } 3419 + 3420 + static int cs_dsp_populate_wseq(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq) 3421 + { 3422 + struct cs_dsp_wseq_op *op = NULL; 3423 + struct cs_dsp_chunk chunk; 3424 + u8 *words; 3425 + int ret; 3426 + 3427 + if (!wseq->ctl) { 3428 + cs_dsp_err(dsp, "No control for write sequence\n"); 3429 + return -EINVAL; 3430 + } 3431 + 3432 + words = kzalloc(wseq->ctl->len, GFP_KERNEL); 3433 + if (!words) 3434 + return -ENOMEM; 3435 + 3436 + ret = cs_dsp_coeff_read_ctrl(wseq->ctl, 0, words, wseq->ctl->len); 3437 + if (ret) { 3438 + cs_dsp_err(dsp, "Failed to read %s: %d\n", wseq->ctl->subname, ret); 3439 + goto err_free; 3440 + } 3441 + 3442 + INIT_LIST_HEAD(&wseq->ops); 3443 + 3444 + chunk = cs_dsp_chunk(words, wseq->ctl->len); 3445 + 3446 + while (!cs_dsp_chunk_end(&chunk)) { 3447 + op = devm_kzalloc(dsp->dev, sizeof(*op), GFP_KERNEL); 3448 + if (!op) { 3449 + ret = -ENOMEM; 3450 + goto err_free; 3451 + } 3452 + 3453 + op->offset = cs_dsp_chunk_bytes(&chunk); 3454 + op->operation = cs_dsp_chunk_read(&chunk, 8); 3455 + 3456 + switch (op->operation) { 3457 + case CS_DSP_WSEQ_END: 3458 + op->data = WSEQ_END_OF_SCRIPT; 3459 + break; 3460 + case CS_DSP_WSEQ_UNLOCK: 3461 + op->data = cs_dsp_chunk_read(&chunk, 16); 3462 + break; 3463 + case CS_DSP_WSEQ_ADDR8: 3464 + op->address = cs_dsp_chunk_read(&chunk, 8); 3465 + op->data = cs_dsp_chunk_read(&chunk, 32); 3466 + break; 3467 + case CS_DSP_WSEQ_H16: 3468 + case CS_DSP_WSEQ_L16: 3469 + op->address = cs_dsp_chunk_read(&chunk, 24); 3470 + op->data = cs_dsp_chunk_read(&chunk, 16); 3471 + break; 3472 + case CS_DSP_WSEQ_FULL: 3473 + op->address = cs_dsp_chunk_read(&chunk, 32); 3474 + op->data = cs_dsp_chunk_read(&chunk, 32); 3475 + break; 3476 + default: 3477 + ret = -EINVAL; 3478 + cs_dsp_err(dsp, "Unsupported op: %X\n", op->operation); 3479 + devm_kfree(dsp->dev, op); 3480 + goto err_free; 3481 + } 3482 + 3483 + list_add_tail(&op->list, &wseq->ops); 3484 + 3485 + if (op->operation == CS_DSP_WSEQ_END) 3486 + break; 3487 + } 3488 + 3489 + if (op && op->operation != CS_DSP_WSEQ_END) { 3490 + cs_dsp_err(dsp, "%s missing end terminator\n", wseq->ctl->subname); 3491 + ret = -ENOENT; 3492 + } 3493 + 3494 + err_free: 3495 + kfree(words); 3496 + 3497 + return ret; 3498 + } 3499 + 3500 + /** 3501 + * cs_dsp_wseq_init() - Initialize write sequences contained within the loaded DSP firmware 3502 + * @dsp: Pointer to DSP structure 3503 + * @wseqs: List of write sequences to initialize 3504 + * @num_wseqs: Number of write sequences to initialize 3505 + * 3506 + * Return: Zero for success, a negative number on error. 3507 + */ 3508 + int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs) 3509 + { 3510 + int i, ret; 3511 + 3512 + lockdep_assert_held(&dsp->pwr_lock); 3513 + 3514 + for (i = 0; i < num_wseqs; i++) { 3515 + ret = cs_dsp_populate_wseq(dsp, &wseqs[i]); 3516 + if (ret) { 3517 + cs_dsp_wseq_clear(dsp, &wseqs[i]); 3518 + return ret; 3519 + } 3520 + } 3521 + 3522 + return 0; 3523 + } 3524 + EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_init, FW_CS_DSP); 3525 + 3526 + static struct cs_dsp_wseq_op *cs_dsp_wseq_find_op(u32 addr, u8 op_code, 3527 + struct list_head *wseq_ops) 3528 + { 3529 + struct cs_dsp_wseq_op *op; 3530 + 3531 + list_for_each_entry(op, wseq_ops, list) { 3532 + if (op->operation == op_code && op->address == addr) 3533 + return op; 3534 + } 3535 + 3536 + return NULL; 3537 + } 3538 + 3539 + /** 3540 + * cs_dsp_wseq_write() - Add or update an entry in a write sequence 3541 + * @dsp: Pointer to a DSP structure 3542 + * @wseq: Write sequence to write to 3543 + * @addr: Address of the register to be written to 3544 + * @data: Data to be written 3545 + * @op_code: The type of operation of the new entry 3546 + * @update: If true, searches for the first entry in the write sequence with 3547 + * the same address and op_code, and replaces it. If false, creates a new entry 3548 + * at the tail 3549 + * 3550 + * This function formats register address and value pairs into the format 3551 + * required for write sequence entries, and either updates or adds the 3552 + * new entry into the write sequence. 3553 + * 3554 + * If update is set to true and no matching entry is found, it will add a new entry. 3555 + * 3556 + * Return: Zero for success, a negative number on error. 3557 + */ 3558 + int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, 3559 + u32 addr, u32 data, u8 op_code, bool update) 3560 + { 3561 + struct cs_dsp_wseq_op *op_end, *op_new = NULL; 3562 + u32 words[WSEQ_OP_MAX_WORDS]; 3563 + struct cs_dsp_chunk chunk; 3564 + int new_op_size, ret; 3565 + 3566 + if (update) 3567 + op_new = cs_dsp_wseq_find_op(addr, op_code, &wseq->ops); 3568 + 3569 + /* If entry to update is not found, treat it as a new operation */ 3570 + if (!op_new) { 3571 + op_end = cs_dsp_wseq_find_op(0, CS_DSP_WSEQ_END, &wseq->ops); 3572 + if (!op_end) { 3573 + cs_dsp_err(dsp, "Missing terminator for %s\n", wseq->ctl->subname); 3574 + return -EINVAL; 3575 + } 3576 + 3577 + op_new = devm_kzalloc(dsp->dev, sizeof(*op_new), GFP_KERNEL); 3578 + if (!op_new) 3579 + return -ENOMEM; 3580 + 3581 + op_new->operation = op_code; 3582 + op_new->address = addr; 3583 + op_new->offset = op_end->offset; 3584 + update = false; 3585 + } 3586 + 3587 + op_new->data = data; 3588 + 3589 + chunk = cs_dsp_chunk(words, sizeof(words)); 3590 + cs_dsp_chunk_write(&chunk, 8, op_new->operation); 3591 + 3592 + switch (op_code) { 3593 + case CS_DSP_WSEQ_FULL: 3594 + cs_dsp_chunk_write(&chunk, 32, op_new->address); 3595 + cs_dsp_chunk_write(&chunk, 32, op_new->data); 3596 + break; 3597 + case CS_DSP_WSEQ_L16: 3598 + case CS_DSP_WSEQ_H16: 3599 + cs_dsp_chunk_write(&chunk, 24, op_new->address); 3600 + cs_dsp_chunk_write(&chunk, 16, op_new->data); 3601 + break; 3602 + default: 3603 + ret = -EINVAL; 3604 + cs_dsp_err(dsp, "Operation %X not supported\n", op_code); 3605 + goto op_new_free; 3606 + } 3607 + 3608 + new_op_size = cs_dsp_chunk_bytes(&chunk); 3609 + 3610 + if (!update) { 3611 + if (wseq->ctl->len - op_end->offset < new_op_size) { 3612 + cs_dsp_err(dsp, "Not enough memory in %s for entry\n", wseq->ctl->subname); 3613 + ret = -E2BIG; 3614 + goto op_new_free; 3615 + } 3616 + 3617 + op_end->offset += new_op_size; 3618 + 3619 + ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_end->offset / sizeof(u32), 3620 + &op_end->data, sizeof(u32)); 3621 + if (ret) 3622 + goto op_new_free; 3623 + 3624 + list_add_tail(&op_new->list, &op_end->list); 3625 + } 3626 + 3627 + ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_new->offset / sizeof(u32), 3628 + words, new_op_size); 3629 + if (ret) 3630 + goto op_new_free; 3631 + 3632 + return 0; 3633 + 3634 + op_new_free: 3635 + devm_kfree(dsp->dev, op_new); 3636 + 3637 + return ret; 3638 + } 3639 + EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_write, FW_CS_DSP); 3640 + 3641 + /** 3642 + * cs_dsp_wseq_multi_write() - Add or update multiple entries in a write sequence 3643 + * @dsp: Pointer to a DSP structure 3644 + * @wseq: Write sequence to write to 3645 + * @reg_seq: List of address-data pairs 3646 + * @num_regs: Number of address-data pairs 3647 + * @op_code: The types of operations of the new entries 3648 + * @update: If true, searches for the first entry in the write sequence with 3649 + * the same address and op_code, and replaces it. If false, creates a new entry 3650 + * at the tail 3651 + * 3652 + * This function calls cs_dsp_wseq_write() for multiple address-data pairs. 3653 + * 3654 + * Return: Zero for success, a negative number on error. 3655 + */ 3656 + int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, 3657 + const struct reg_sequence *reg_seq, int num_regs, 3658 + u8 op_code, bool update) 3659 + { 3660 + int i, ret; 3661 + 3662 + for (i = 0; i < num_regs; i++) { 3663 + ret = cs_dsp_wseq_write(dsp, wseq, reg_seq[i].reg, 3664 + reg_seq[i].def, op_code, update); 3665 + if (ret) 3666 + return ret; 3667 + } 3668 + 3669 + return 0; 3670 + } 3671 + EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_multi_write, FW_CS_DSP); 3406 3672 3407 3673 MODULE_DESCRIPTION("Cirrus Logic DSP Support"); 3408 3674 MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+10
drivers/input/misc/Kconfig
··· 140 140 To compile this driver as a module, choose M here: the 141 141 module will be called bma150. 142 142 143 + config INPUT_CS40L50_VIBRA 144 + tristate "CS40L50 Haptic Driver support" 145 + depends on MFD_CS40L50_CORE 146 + help 147 + Say Y here to enable support for Cirrus Logic's CS40L50 148 + haptic driver. 149 + 150 + To compile this driver as a module, choose M here: the 151 + module will be called cs40l50-vibra. 152 + 143 153 config INPUT_E3X0_BUTTON 144 154 tristate "NI Ettus Research USRP E3xx Button support." 145 155 default n
+1
drivers/input/misc/Makefile
··· 28 28 obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o 29 29 obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o 30 30 obj-$(CONFIG_INPUT_CPCAP_PWRBUTTON) += cpcap-pwrbutton.o 31 + obj-$(CONFIG_INPUT_CS40L50_VIBRA) += cs40l50-vibra.o 31 32 obj-$(CONFIG_INPUT_DA7280_HAPTICS) += da7280.o 32 33 obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o 33 34 obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
+555
drivers/input/misc/cs40l50-vibra.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/bitfield.h> 12 + #include <linux/input.h> 13 + #include <linux/mfd/cs40l50.h> 14 + #include <linux/platform_device.h> 15 + #include <linux/pm_runtime.h> 16 + 17 + /* Wavetables */ 18 + #define CS40L50_RAM_INDEX_START 0x1000000 19 + #define CS40L50_RAM_INDEX_END 0x100007F 20 + #define CS40L50_RTH_INDEX_START 0x1400000 21 + #define CS40L50_RTH_INDEX_END 0x1400001 22 + #define CS40L50_ROM_INDEX_START 0x1800000 23 + #define CS40L50_ROM_INDEX_END 0x180001A 24 + #define CS40L50_TYPE_PCM 8 25 + #define CS40L50_TYPE_PWLE 12 26 + #define CS40L50_PCM_ID 0x0 27 + #define CS40L50_OWT_CUSTOM_DATA_SIZE 2 28 + #define CS40L50_CUSTOM_DATA_MASK 0xFFFFU 29 + 30 + /* DSP */ 31 + #define CS40L50_GPIO_BASE 0x2804140 32 + #define CS40L50_OWT_BASE 0x2805C34 33 + #define CS40L50_OWT_SIZE 0x2805C38 34 + #define CS40L50_OWT_NEXT 0x2805C3C 35 + #define CS40L50_EFFECTS_MAX 1 36 + 37 + /* GPIO */ 38 + #define CS40L50_GPIO_NUM_MASK GENMASK(14, 12) 39 + #define CS40L50_GPIO_EDGE_MASK BIT(15) 40 + #define CS40L50_GPIO_MAPPING_NONE 0 41 + #define CS40L50_GPIO_DISABLE 0x1FF 42 + 43 + enum cs40l50_bank_type { 44 + CS40L50_WVFRM_BANK_RAM, 45 + CS40L50_WVFRM_BANK_ROM, 46 + CS40L50_WVFRM_BANK_OWT, 47 + CS40L50_WVFRM_BANK_NUM, 48 + }; 49 + 50 + /* Describes an area in DSP memory populated by effects */ 51 + struct cs40l50_bank { 52 + enum cs40l50_bank_type type; 53 + u32 base_index; 54 + u32 max_index; 55 + }; 56 + 57 + struct cs40l50_effect { 58 + enum cs40l50_bank_type type; 59 + struct list_head list; 60 + u32 gpio_reg; 61 + u32 index; 62 + int id; 63 + }; 64 + 65 + /* Describes haptic interface of loaded DSP firmware */ 66 + struct cs40l50_vibra_dsp { 67 + struct cs40l50_bank *banks; 68 + u32 gpio_base_reg; 69 + u32 owt_offset_reg; 70 + u32 owt_size_reg; 71 + u32 owt_base_reg; 72 + u32 push_owt_cmd; 73 + u32 delete_owt_cmd; 74 + u32 stop_cmd; 75 + int (*write)(struct device *dev, struct regmap *regmap, u32 val); 76 + }; 77 + 78 + /* Describes configuration and state of haptic operations */ 79 + struct cs40l50_vibra { 80 + struct device *dev; 81 + struct regmap *regmap; 82 + struct input_dev *input; 83 + struct workqueue_struct *vib_wq; 84 + struct list_head effect_head; 85 + struct cs40l50_vibra_dsp dsp; 86 + }; 87 + 88 + struct cs40l50_work { 89 + struct cs40l50_vibra *vib; 90 + struct ff_effect *effect; 91 + struct work_struct work; 92 + s16 *custom_data; 93 + int custom_len; 94 + int count; 95 + int error; 96 + }; 97 + 98 + static struct cs40l50_bank cs40l50_banks[] = { 99 + { 100 + .type = CS40L50_WVFRM_BANK_RAM, 101 + .base_index = CS40L50_RAM_INDEX_START, 102 + .max_index = CS40L50_RAM_INDEX_END, 103 + }, 104 + { 105 + .type = CS40L50_WVFRM_BANK_ROM, 106 + .base_index = CS40L50_ROM_INDEX_START, 107 + .max_index = CS40L50_ROM_INDEX_END, 108 + }, 109 + { 110 + .type = CS40L50_WVFRM_BANK_OWT, 111 + .base_index = CS40L50_RTH_INDEX_START, 112 + .max_index = CS40L50_RTH_INDEX_END, 113 + }, 114 + }; 115 + 116 + static struct cs40l50_vibra_dsp cs40l50_dsp = { 117 + .banks = cs40l50_banks, 118 + .gpio_base_reg = CS40L50_GPIO_BASE, 119 + .owt_base_reg = CS40L50_OWT_BASE, 120 + .owt_offset_reg = CS40L50_OWT_NEXT, 121 + .owt_size_reg = CS40L50_OWT_SIZE, 122 + .push_owt_cmd = CS40L50_OWT_PUSH, 123 + .delete_owt_cmd = CS40L50_OWT_DELETE, 124 + .stop_cmd = CS40L50_STOP_PLAYBACK, 125 + .write = cs40l50_dsp_write, 126 + }; 127 + 128 + static struct cs40l50_effect *cs40l50_find_effect(int id, struct list_head *effect_head) 129 + { 130 + struct cs40l50_effect *effect; 131 + 132 + list_for_each_entry(effect, effect_head, list) 133 + if (effect->id == id) 134 + return effect; 135 + 136 + return NULL; 137 + } 138 + 139 + static int cs40l50_effect_bank_set(struct cs40l50_work *work_data, 140 + struct cs40l50_effect *effect) 141 + { 142 + s16 bank_type = work_data->custom_data[0] & CS40L50_CUSTOM_DATA_MASK; 143 + 144 + if (bank_type >= CS40L50_WVFRM_BANK_NUM) { 145 + dev_err(work_data->vib->dev, "Invalid bank (%d)\n", bank_type); 146 + return -EINVAL; 147 + } 148 + 149 + if (work_data->custom_len > CS40L50_OWT_CUSTOM_DATA_SIZE) 150 + effect->type = CS40L50_WVFRM_BANK_OWT; 151 + else 152 + effect->type = bank_type; 153 + 154 + return 0; 155 + } 156 + 157 + static int cs40l50_effect_index_set(struct cs40l50_work *work_data, 158 + struct cs40l50_effect *effect) 159 + { 160 + struct cs40l50_vibra *vib = work_data->vib; 161 + struct cs40l50_effect *owt_effect; 162 + u32 base_index, max_index; 163 + 164 + base_index = vib->dsp.banks[effect->type].base_index; 165 + max_index = vib->dsp.banks[effect->type].max_index; 166 + 167 + effect->index = base_index; 168 + 169 + switch (effect->type) { 170 + case CS40L50_WVFRM_BANK_OWT: 171 + list_for_each_entry(owt_effect, &vib->effect_head, list) 172 + if (owt_effect->type == CS40L50_WVFRM_BANK_OWT) 173 + effect->index++; 174 + break; 175 + case CS40L50_WVFRM_BANK_ROM: 176 + case CS40L50_WVFRM_BANK_RAM: 177 + effect->index += work_data->custom_data[1] & CS40L50_CUSTOM_DATA_MASK; 178 + break; 179 + default: 180 + dev_err(vib->dev, "Bank type %d not supported\n", effect->type); 181 + return -EINVAL; 182 + } 183 + 184 + if (effect->index > max_index || effect->index < base_index) { 185 + dev_err(vib->dev, "Index out of bounds: %u\n", effect->index); 186 + return -ENOSPC; 187 + } 188 + 189 + return 0; 190 + } 191 + 192 + static int cs40l50_effect_gpio_mapping_set(struct cs40l50_work *work_data, 193 + struct cs40l50_effect *effect) 194 + { 195 + u16 gpio_edge, gpio_num, button = work_data->effect->trigger.button; 196 + struct cs40l50_vibra *vib = work_data->vib; 197 + 198 + if (button) { 199 + gpio_num = FIELD_GET(CS40L50_GPIO_NUM_MASK, button); 200 + gpio_edge = FIELD_GET(CS40L50_GPIO_EDGE_MASK, button); 201 + effect->gpio_reg = vib->dsp.gpio_base_reg + (gpio_num * 8) - gpio_edge; 202 + 203 + return regmap_write(vib->regmap, effect->gpio_reg, button); 204 + } 205 + 206 + effect->gpio_reg = CS40L50_GPIO_MAPPING_NONE; 207 + 208 + return 0; 209 + } 210 + 211 + struct cs40l50_owt_header { 212 + u32 type; 213 + u32 data_words; 214 + u32 offset; 215 + } __packed; 216 + 217 + static int cs40l50_upload_owt(struct cs40l50_work *work_data) 218 + { 219 + u8 *new_owt_effect_data __free(kfree) = NULL; 220 + struct cs40l50_vibra *vib = work_data->vib; 221 + size_t len = work_data->custom_len * 2; 222 + struct cs40l50_owt_header header; 223 + u32 offset, size; 224 + int error; 225 + 226 + error = regmap_read(vib->regmap, vib->dsp.owt_size_reg, &size); 227 + if (error) 228 + return error; 229 + 230 + if ((size * sizeof(u32)) < sizeof(header) + len) { 231 + dev_err(vib->dev, "No space in open wavetable for effect\n"); 232 + return -ENOSPC; 233 + } 234 + 235 + header.type = work_data->custom_data[0] == CS40L50_PCM_ID ? CS40L50_TYPE_PCM : 236 + CS40L50_TYPE_PWLE; 237 + header.offset = sizeof(header) / sizeof(u32); 238 + header.data_words = len / sizeof(u32); 239 + 240 + new_owt_effect_data = kmalloc(sizeof(header) + len, GFP_KERNEL); 241 + 242 + memcpy(new_owt_effect_data, &header, sizeof(header)); 243 + memcpy(new_owt_effect_data + sizeof(header), work_data->custom_data, len); 244 + 245 + error = regmap_read(vib->regmap, vib->dsp.owt_offset_reg, &offset); 246 + if (error) 247 + return error; 248 + 249 + error = regmap_bulk_write(vib->regmap, vib->dsp.owt_base_reg + 250 + (offset * sizeof(u32)), new_owt_effect_data, 251 + sizeof(header) + len); 252 + if (error) 253 + return error; 254 + 255 + error = vib->dsp.write(vib->dev, vib->regmap, vib->dsp.push_owt_cmd); 256 + if (error) 257 + return error; 258 + 259 + return 0; 260 + } 261 + 262 + static void cs40l50_add_worker(struct work_struct *work) 263 + { 264 + struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work); 265 + struct cs40l50_vibra *vib = work_data->vib; 266 + struct cs40l50_effect *effect; 267 + bool is_new = false; 268 + int error; 269 + 270 + error = pm_runtime_resume_and_get(vib->dev); 271 + if (error) 272 + goto err_exit; 273 + 274 + /* Update effect if already uploaded, otherwise create new effect */ 275 + effect = cs40l50_find_effect(work_data->effect->id, &vib->effect_head); 276 + if (!effect) { 277 + effect = kzalloc(sizeof(*effect), GFP_KERNEL); 278 + if (!effect) { 279 + error = -ENOMEM; 280 + goto err_pm; 281 + } 282 + 283 + effect->id = work_data->effect->id; 284 + is_new = true; 285 + } 286 + 287 + error = cs40l50_effect_bank_set(work_data, effect); 288 + if (error) 289 + goto err_free; 290 + 291 + error = cs40l50_effect_index_set(work_data, effect); 292 + if (error) 293 + goto err_free; 294 + 295 + error = cs40l50_effect_gpio_mapping_set(work_data, effect); 296 + if (error) 297 + goto err_free; 298 + 299 + if (effect->type == CS40L50_WVFRM_BANK_OWT) 300 + error = cs40l50_upload_owt(work_data); 301 + err_free: 302 + if (is_new) { 303 + if (error) 304 + kfree(effect); 305 + else 306 + list_add(&effect->list, &vib->effect_head); 307 + } 308 + err_pm: 309 + pm_runtime_mark_last_busy(vib->dev); 310 + pm_runtime_put_autosuspend(vib->dev); 311 + err_exit: 312 + work_data->error = error; 313 + } 314 + 315 + static int cs40l50_add(struct input_dev *dev, struct ff_effect *effect, 316 + struct ff_effect *old) 317 + { 318 + struct ff_periodic_effect *periodic = &effect->u.periodic; 319 + struct cs40l50_vibra *vib = input_get_drvdata(dev); 320 + struct cs40l50_work work_data; 321 + 322 + if (effect->type != FF_PERIODIC || periodic->waveform != FF_CUSTOM) { 323 + dev_err(vib->dev, "Type (%#X) or waveform (%#X) unsupported\n", 324 + effect->type, periodic->waveform); 325 + return -EINVAL; 326 + } 327 + 328 + work_data.custom_data = memdup_array_user(effect->u.periodic.custom_data, 329 + effect->u.periodic.custom_len, 330 + sizeof(s16)); 331 + if (IS_ERR(work_data.custom_data)) 332 + return PTR_ERR(work_data.custom_data); 333 + 334 + work_data.custom_len = effect->u.periodic.custom_len; 335 + work_data.vib = vib; 336 + work_data.effect = effect; 337 + INIT_WORK(&work_data.work, cs40l50_add_worker); 338 + 339 + /* Push to the workqueue to serialize with playbacks */ 340 + queue_work(vib->vib_wq, &work_data.work); 341 + flush_work(&work_data.work); 342 + 343 + kfree(work_data.custom_data); 344 + 345 + return work_data.error; 346 + } 347 + 348 + static void cs40l50_start_worker(struct work_struct *work) 349 + { 350 + struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work); 351 + struct cs40l50_vibra *vib = work_data->vib; 352 + struct cs40l50_effect *start_effect; 353 + 354 + if (pm_runtime_resume_and_get(vib->dev) < 0) 355 + goto err_free; 356 + 357 + start_effect = cs40l50_find_effect(work_data->effect->id, &vib->effect_head); 358 + if (start_effect) { 359 + while (--work_data->count >= 0) { 360 + vib->dsp.write(vib->dev, vib->regmap, start_effect->index); 361 + usleep_range(work_data->effect->replay.length, 362 + work_data->effect->replay.length + 100); 363 + } 364 + } else { 365 + dev_err(vib->dev, "Effect to play not found\n"); 366 + } 367 + 368 + pm_runtime_mark_last_busy(vib->dev); 369 + pm_runtime_put_autosuspend(vib->dev); 370 + err_free: 371 + kfree(work_data); 372 + } 373 + 374 + static void cs40l50_stop_worker(struct work_struct *work) 375 + { 376 + struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work); 377 + struct cs40l50_vibra *vib = work_data->vib; 378 + 379 + if (pm_runtime_resume_and_get(vib->dev) < 0) 380 + return; 381 + 382 + vib->dsp.write(vib->dev, vib->regmap, vib->dsp.stop_cmd); 383 + 384 + pm_runtime_mark_last_busy(vib->dev); 385 + pm_runtime_put_autosuspend(vib->dev); 386 + 387 + kfree(work_data); 388 + } 389 + 390 + static int cs40l50_playback(struct input_dev *dev, int effect_id, int val) 391 + { 392 + struct cs40l50_vibra *vib = input_get_drvdata(dev); 393 + struct cs40l50_work *work_data; 394 + 395 + work_data = kzalloc(sizeof(*work_data), GFP_ATOMIC); 396 + if (!work_data) 397 + return -ENOMEM; 398 + 399 + work_data->vib = vib; 400 + 401 + if (val > 0) { 402 + work_data->effect = &dev->ff->effects[effect_id]; 403 + work_data->count = val; 404 + INIT_WORK(&work_data->work, cs40l50_start_worker); 405 + } else { 406 + /* Stop the amplifier as device drives only one effect */ 407 + INIT_WORK(&work_data->work, cs40l50_stop_worker); 408 + } 409 + 410 + queue_work(vib->vib_wq, &work_data->work); 411 + 412 + return 0; 413 + } 414 + 415 + static void cs40l50_erase_worker(struct work_struct *work) 416 + { 417 + struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work); 418 + struct cs40l50_effect *erase_effect, *owt_effect; 419 + struct cs40l50_vibra *vib = work_data->vib; 420 + int error; 421 + 422 + error = pm_runtime_resume_and_get(vib->dev); 423 + if (error) 424 + goto err_exit; 425 + 426 + erase_effect = cs40l50_find_effect(work_data->effect->id, &vib->effect_head); 427 + if (!erase_effect) { 428 + dev_err(vib->dev, "Effect to erase not found\n"); 429 + error = -EINVAL; 430 + goto err_pm; 431 + } 432 + 433 + if (erase_effect->gpio_reg != CS40L50_GPIO_MAPPING_NONE) { 434 + error = regmap_write(vib->regmap, erase_effect->gpio_reg, 435 + CS40L50_GPIO_DISABLE); 436 + if (error) 437 + goto err_pm; 438 + } 439 + 440 + if (erase_effect->type == CS40L50_WVFRM_BANK_OWT) { 441 + error = vib->dsp.write(vib->dev, vib->regmap, 442 + vib->dsp.delete_owt_cmd | 443 + (erase_effect->index & 0xFF)); 444 + if (error) 445 + goto err_pm; 446 + 447 + list_for_each_entry(owt_effect, &vib->effect_head, list) 448 + if (owt_effect->type == CS40L50_WVFRM_BANK_OWT && 449 + owt_effect->index > erase_effect->index) 450 + owt_effect->index--; 451 + } 452 + 453 + list_del(&erase_effect->list); 454 + kfree(erase_effect); 455 + err_pm: 456 + pm_runtime_mark_last_busy(vib->dev); 457 + pm_runtime_put_autosuspend(vib->dev); 458 + err_exit: 459 + work_data->error = error; 460 + } 461 + 462 + static int cs40l50_erase(struct input_dev *dev, int effect_id) 463 + { 464 + struct cs40l50_vibra *vib = input_get_drvdata(dev); 465 + struct cs40l50_work work_data; 466 + 467 + work_data.vib = vib; 468 + work_data.effect = &dev->ff->effects[effect_id]; 469 + 470 + INIT_WORK(&work_data.work, cs40l50_erase_worker); 471 + 472 + /* Push to workqueue to serialize with playbacks */ 473 + queue_work(vib->vib_wq, &work_data.work); 474 + flush_work(&work_data.work); 475 + 476 + return work_data.error; 477 + } 478 + 479 + static void cs40l50_remove_wq(void *data) 480 + { 481 + flush_workqueue(data); 482 + destroy_workqueue(data); 483 + } 484 + 485 + static int cs40l50_vibra_probe(struct platform_device *pdev) 486 + { 487 + struct cs40l50 *cs40l50 = dev_get_drvdata(pdev->dev.parent); 488 + struct cs40l50_vibra *vib; 489 + int error; 490 + 491 + vib = devm_kzalloc(pdev->dev.parent, sizeof(*vib), GFP_KERNEL); 492 + if (!vib) 493 + return -ENOMEM; 494 + 495 + vib->dev = cs40l50->dev; 496 + vib->regmap = cs40l50->regmap; 497 + vib->dsp = cs40l50_dsp; 498 + 499 + vib->input = devm_input_allocate_device(vib->dev); 500 + if (!vib->input) 501 + return -ENOMEM; 502 + 503 + vib->input->id.product = cs40l50->devid; 504 + vib->input->id.version = cs40l50->revid; 505 + vib->input->name = "cs40l50_vibra"; 506 + 507 + input_set_drvdata(vib->input, vib); 508 + input_set_capability(vib->input, EV_FF, FF_PERIODIC); 509 + input_set_capability(vib->input, EV_FF, FF_CUSTOM); 510 + 511 + error = input_ff_create(vib->input, CS40L50_EFFECTS_MAX); 512 + if (error) { 513 + dev_err(vib->dev, "Failed to create input device\n"); 514 + return error; 515 + } 516 + 517 + vib->input->ff->upload = cs40l50_add; 518 + vib->input->ff->playback = cs40l50_playback; 519 + vib->input->ff->erase = cs40l50_erase; 520 + 521 + INIT_LIST_HEAD(&vib->effect_head); 522 + 523 + vib->vib_wq = alloc_ordered_workqueue("vib_wq", WQ_HIGHPRI); 524 + if (!vib->vib_wq) 525 + return -ENOMEM; 526 + 527 + error = devm_add_action_or_reset(vib->dev, cs40l50_remove_wq, vib->vib_wq); 528 + if (error) 529 + return error; 530 + 531 + error = input_register_device(vib->input); 532 + if (error) 533 + return error; 534 + 535 + return 0; 536 + } 537 + 538 + static const struct platform_device_id cs40l50_vibra_id_match[] = { 539 + { "cs40l50-vibra", }, 540 + {} 541 + }; 542 + MODULE_DEVICE_TABLE(platform, cs40l50_vibra_id_match); 543 + 544 + static struct platform_driver cs40l50_vibra_driver = { 545 + .probe = cs40l50_vibra_probe, 546 + .id_table = cs40l50_vibra_id_match, 547 + .driver = { 548 + .name = "cs40l50-vibra", 549 + }, 550 + }; 551 + module_platform_driver(cs40l50_vibra_driver); 552 + 553 + MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver"); 554 + MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>"); 555 + MODULE_LICENSE("GPL");
+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");
+27
include/linux/firmware/cirrus/cs_dsp.h
··· 42 42 #define CS_DSP_ACKED_CTL_MIN_VALUE 0 43 43 #define CS_DSP_ACKED_CTL_MAX_VALUE 0xFFFFFF 44 44 45 + /* 46 + * Write sequence operation codes 47 + */ 48 + #define CS_DSP_WSEQ_FULL 0x00 49 + #define CS_DSP_WSEQ_ADDR8 0x02 50 + #define CS_DSP_WSEQ_L16 0x04 51 + #define CS_DSP_WSEQ_H16 0x05 52 + #define CS_DSP_WSEQ_UNLOCK 0xFD 53 + #define CS_DSP_WSEQ_END 0xFF 54 + 45 55 /** 46 56 * struct cs_dsp_region - Describes a logical memory region in DSP address space 47 57 * @type: Memory region type ··· 267 257 int type, unsigned int id); 268 258 269 259 const char *cs_dsp_mem_region_name(unsigned int type); 260 + 261 + /** 262 + * struct cs_dsp_wseq - Describes a write sequence 263 + * @ctl: Write sequence cs_dsp control 264 + * @ops: Operations contained within 265 + */ 266 + struct cs_dsp_wseq { 267 + struct cs_dsp_coeff_ctl *ctl; 268 + struct list_head ops; 269 + }; 270 + 271 + int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs); 272 + int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, u32 addr, u32 data, 273 + u8 op_code, bool update); 274 + int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, 275 + const struct reg_sequence *reg_seq, int num_regs, 276 + u8 op_code, bool update); 270 277 271 278 /** 272 279 * struct cs_dsp_chunk - Describes a buffer holding data formatted for the DSP
+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__ */
+11
sound/soc/codecs/Kconfig
··· 75 75 imply SND_SOC_CS35L56_I2C 76 76 imply SND_SOC_CS35L56_SPI 77 77 imply SND_SOC_CS35L56_SDW 78 + imply SND_SOC_CS40L50 78 79 imply SND_SOC_CS42L42 79 80 imply SND_SOC_CS42L42_SDW 80 81 imply SND_SOC_CS42L43 ··· 847 846 select SND_SOC_CS35L56_SHARED 848 847 help 849 848 Enable support for Cirrus Logic CS35L56 boosted amplifier with SoundWire control 849 + 850 + config SND_SOC_CS40L50 851 + tristate "Cirrus Logic CS40L50 CODEC" 852 + depends on MFD_CS40L50_CORE 853 + help 854 + This option enables support for I2S streaming to Cirrus Logic CS40L50. 855 + 856 + CS40L50 is a haptic driver with waveform memory, an integrated 857 + DSP, and closed-loop algorithms. If built as a module, it will be 858 + called snd-soc-cs40l50. 850 859 851 860 config SND_SOC_CS42L42_CORE 852 861 tristate
+2
sound/soc/codecs/Makefile
··· 78 78 snd-soc-cs35l56-i2c-y := cs35l56-i2c.o 79 79 snd-soc-cs35l56-spi-y := cs35l56-spi.o 80 80 snd-soc-cs35l56-sdw-y := cs35l56-sdw.o 81 + snd-soc-cs40l50-objs := cs40l50-codec.o 81 82 snd-soc-cs42l42-y := cs42l42.o 82 83 snd-soc-cs42l42-i2c-y := cs42l42-i2c.o 83 84 snd-soc-cs42l42-sdw-y := cs42l42-sdw.o ··· 476 475 obj-$(CONFIG_SND_SOC_CS35L56_I2C) += snd-soc-cs35l56-i2c.o 477 476 obj-$(CONFIG_SND_SOC_CS35L56_SPI) += snd-soc-cs35l56-spi.o 478 477 obj-$(CONFIG_SND_SOC_CS35L56_SDW) += snd-soc-cs35l56-sdw.o 478 + obj-$(CONFIG_SND_SOC_CS40L50) += snd-soc-cs40l50.o 479 479 obj-$(CONFIG_SND_SOC_CS42L42_CORE) += snd-soc-cs42l42.o 480 480 obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42-i2c.o 481 481 obj-$(CONFIG_SND_SOC_CS42L42_SDW) += snd-soc-cs42l42-sdw.o
+307
sound/soc/codecs/cs40l50-codec.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 + #include <linux/bitfield.h> 11 + #include <linux/mfd/cs40l50.h> 12 + #include <sound/pcm_params.h> 13 + #include <sound/soc.h> 14 + 15 + #define CS40L50_REFCLK_INPUT 0x2C04 16 + #define CS40L50_ASP_CONTROL2 0x4808 17 + #define CS40L50_ASP_DATA_CONTROL5 0x4840 18 + 19 + /* PLL Config */ 20 + #define CS40L50_PLL_REFCLK_BCLK 0x0 21 + #define CS40L50_PLL_REFCLK_MCLK 0x5 22 + #define CS40L50_PLL_REEFCLK_MCLK_CFG 0x00 23 + #define CS40L50_PLL_REFCLK_LOOP_MASK BIT(11) 24 + #define CS40L50_PLL_REFCLK_OPEN_LOOP 1 25 + #define CS40L50_PLL_REFCLK_CLOSED_LOOP 0 26 + #define CS40L50_PLL_REFCLK_LOOP_SHIFT 11 27 + #define CS40L50_PLL_REFCLK_FREQ_MASK GENMASK(10, 5) 28 + #define CS40L50_PLL_REFCLK_FREQ_SHIFT 5 29 + #define CS40L50_PLL_REFCLK_SEL_MASK GENMASK(2, 0) 30 + #define CS40L50_BCLK_RATIO_DEFAULT 32 31 + 32 + /* ASP Config */ 33 + #define CS40L50_ASP_RX_WIDTH_SHIFT 24 34 + #define CS40L50_ASP_RX_WIDTH_MASK GENMASK(31, 24) 35 + #define CS40L50_ASP_RX_WL_MASK GENMASK(5, 0) 36 + #define CS40L50_ASP_FSYNC_INV_MASK BIT(2) 37 + #define CS40L50_ASP_BCLK_INV_MASK BIT(6) 38 + #define CS40L50_ASP_FMT_MASK GENMASK(10, 8) 39 + #define CS40L50_ASP_FMT_I2S 0x2 40 + 41 + struct cs40l50_pll_config { 42 + unsigned int freq; 43 + unsigned int cfg; 44 + }; 45 + 46 + struct cs40l50_codec { 47 + struct device *dev; 48 + struct regmap *regmap; 49 + unsigned int daifmt; 50 + unsigned int bclk_ratio; 51 + unsigned int rate; 52 + }; 53 + 54 + static const struct cs40l50_pll_config cs40l50_pll_cfg[] = { 55 + { 32768, 0x00 }, 56 + { 1536000, 0x1B }, 57 + { 3072000, 0x21 }, 58 + { 6144000, 0x28 }, 59 + { 9600000, 0x30 }, 60 + { 12288000, 0x33 }, 61 + }; 62 + 63 + static int cs40l50_get_clk_config(const unsigned int freq, unsigned int *cfg) 64 + { 65 + int i; 66 + 67 + for (i = 0; i < ARRAY_SIZE(cs40l50_pll_cfg); i++) { 68 + if (cs40l50_pll_cfg[i].freq == freq) { 69 + *cfg = cs40l50_pll_cfg[i].cfg; 70 + return 0; 71 + } 72 + } 73 + 74 + return -EINVAL; 75 + } 76 + 77 + static int cs40l50_swap_ext_clk(struct cs40l50_codec *codec, const unsigned int clk_src) 78 + { 79 + unsigned int cfg; 80 + int ret; 81 + 82 + switch (clk_src) { 83 + case CS40L50_PLL_REFCLK_BCLK: 84 + ret = cs40l50_get_clk_config(codec->bclk_ratio * codec->rate, &cfg); 85 + if (ret) 86 + return ret; 87 + break; 88 + case CS40L50_PLL_REFCLK_MCLK: 89 + cfg = CS40L50_PLL_REEFCLK_MCLK_CFG; 90 + break; 91 + default: 92 + return -EINVAL; 93 + } 94 + 95 + ret = regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT, 96 + CS40L50_PLL_REFCLK_LOOP_MASK, 97 + CS40L50_PLL_REFCLK_OPEN_LOOP << 98 + CS40L50_PLL_REFCLK_LOOP_SHIFT); 99 + if (ret) 100 + return ret; 101 + 102 + ret = regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT, 103 + CS40L50_PLL_REFCLK_FREQ_MASK | 104 + CS40L50_PLL_REFCLK_SEL_MASK, 105 + (cfg << CS40L50_PLL_REFCLK_FREQ_SHIFT) | clk_src); 106 + if (ret) 107 + return ret; 108 + 109 + return regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT, 110 + CS40L50_PLL_REFCLK_LOOP_MASK, 111 + CS40L50_PLL_REFCLK_CLOSED_LOOP << 112 + CS40L50_PLL_REFCLK_LOOP_SHIFT); 113 + } 114 + 115 + static int cs40l50_clk_en(struct snd_soc_dapm_widget *w, 116 + struct snd_kcontrol *kcontrol, 117 + int event) 118 + { 119 + struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm); 120 + struct cs40l50_codec *codec = snd_soc_component_get_drvdata(comp); 121 + int ret; 122 + 123 + switch (event) { 124 + case SND_SOC_DAPM_POST_PMU: 125 + ret = cs40l50_dsp_write(codec->dev, codec->regmap, CS40L50_STOP_PLAYBACK); 126 + if (ret) 127 + return ret; 128 + 129 + ret = cs40l50_dsp_write(codec->dev, codec->regmap, CS40L50_START_I2S); 130 + if (ret) 131 + return ret; 132 + 133 + ret = cs40l50_swap_ext_clk(codec, CS40L50_PLL_REFCLK_BCLK); 134 + if (ret) 135 + return ret; 136 + break; 137 + case SND_SOC_DAPM_PRE_PMD: 138 + ret = cs40l50_swap_ext_clk(codec, CS40L50_PLL_REFCLK_MCLK); 139 + if (ret) 140 + return ret; 141 + break; 142 + default: 143 + return -EINVAL; 144 + } 145 + 146 + return 0; 147 + } 148 + 149 + static const struct snd_soc_dapm_widget cs40l50_dapm_widgets[] = { 150 + SND_SOC_DAPM_SUPPLY_S("ASP PLL", 0, SND_SOC_NOPM, 0, 0, cs40l50_clk_en, 151 + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), 152 + SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, SND_SOC_NOPM, 0, 0), 153 + SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, SND_SOC_NOPM, 0, 0), 154 + SND_SOC_DAPM_OUTPUT("OUT"), 155 + }; 156 + 157 + static const struct snd_soc_dapm_route cs40l50_dapm_routes[] = { 158 + { "ASP Playback", NULL, "ASP PLL" }, 159 + { "ASPRX1", NULL, "ASP Playback" }, 160 + { "ASPRX2", NULL, "ASP Playback" }, 161 + 162 + { "OUT", NULL, "ASPRX1" }, 163 + { "OUT", NULL, "ASPRX2" }, 164 + }; 165 + 166 + static int cs40l50_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) 167 + { 168 + struct cs40l50_codec *codec = snd_soc_component_get_drvdata(codec_dai->component); 169 + 170 + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBC_CFC) 171 + return -EINVAL; 172 + 173 + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 174 + case SND_SOC_DAIFMT_NB_NF: 175 + codec->daifmt = 0; 176 + break; 177 + case SND_SOC_DAIFMT_NB_IF: 178 + codec->daifmt = CS40L50_ASP_FSYNC_INV_MASK; 179 + break; 180 + case SND_SOC_DAIFMT_IB_NF: 181 + codec->daifmt = CS40L50_ASP_BCLK_INV_MASK; 182 + break; 183 + case SND_SOC_DAIFMT_IB_IF: 184 + codec->daifmt = CS40L50_ASP_FSYNC_INV_MASK | CS40L50_ASP_BCLK_INV_MASK; 185 + break; 186 + default: 187 + dev_err(codec->dev, "Invalid clock invert\n"); 188 + return -EINVAL; 189 + } 190 + 191 + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 192 + case SND_SOC_DAIFMT_I2S: 193 + codec->daifmt |= FIELD_PREP(CS40L50_ASP_FMT_MASK, CS40L50_ASP_FMT_I2S); 194 + break; 195 + default: 196 + dev_err(codec->dev, "Unsupported DAI format\n"); 197 + return -EINVAL; 198 + } 199 + 200 + return 0; 201 + } 202 + 203 + static int cs40l50_hw_params(struct snd_pcm_substream *substream, 204 + struct snd_pcm_hw_params *params, 205 + struct snd_soc_dai *dai) 206 + { 207 + struct cs40l50_codec *codec = snd_soc_component_get_drvdata(dai->component); 208 + unsigned int asp_rx_wl = params_width(params); 209 + int ret; 210 + 211 + codec->rate = params_rate(params); 212 + 213 + ret = regmap_update_bits(codec->regmap, CS40L50_ASP_DATA_CONTROL5, 214 + CS40L50_ASP_RX_WL_MASK, asp_rx_wl); 215 + if (ret) 216 + return ret; 217 + 218 + codec->daifmt |= (asp_rx_wl << CS40L50_ASP_RX_WIDTH_SHIFT); 219 + 220 + return regmap_update_bits(codec->regmap, CS40L50_ASP_CONTROL2, 221 + CS40L50_ASP_FSYNC_INV_MASK | 222 + CS40L50_ASP_BCLK_INV_MASK | 223 + CS40L50_ASP_FMT_MASK | 224 + CS40L50_ASP_RX_WIDTH_MASK, codec->daifmt); 225 + } 226 + 227 + static int cs40l50_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) 228 + { 229 + struct cs40l50_codec *codec = snd_soc_component_get_drvdata(dai->component); 230 + 231 + codec->bclk_ratio = ratio; 232 + 233 + return 0; 234 + } 235 + 236 + static const struct snd_soc_dai_ops cs40l50_dai_ops = { 237 + .set_fmt = cs40l50_set_dai_fmt, 238 + .set_bclk_ratio = cs40l50_set_dai_bclk_ratio, 239 + .hw_params = cs40l50_hw_params, 240 + }; 241 + 242 + static struct snd_soc_dai_driver cs40l50_dai[] = { 243 + { 244 + .name = "cs40l50-pcm", 245 + .id = 0, 246 + .playback = { 247 + .stream_name = "ASP Playback", 248 + .channels_min = 1, 249 + .channels_max = 2, 250 + .rates = SNDRV_PCM_RATE_48000, 251 + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, 252 + }, 253 + .ops = &cs40l50_dai_ops, 254 + }, 255 + }; 256 + 257 + static int cs40l50_codec_probe(struct snd_soc_component *component) 258 + { 259 + struct cs40l50_codec *codec = snd_soc_component_get_drvdata(component); 260 + 261 + codec->bclk_ratio = CS40L50_BCLK_RATIO_DEFAULT; 262 + 263 + return 0; 264 + } 265 + 266 + static const struct snd_soc_component_driver soc_codec_dev_cs40l50 = { 267 + .probe = cs40l50_codec_probe, 268 + .dapm_widgets = cs40l50_dapm_widgets, 269 + .num_dapm_widgets = ARRAY_SIZE(cs40l50_dapm_widgets), 270 + .dapm_routes = cs40l50_dapm_routes, 271 + .num_dapm_routes = ARRAY_SIZE(cs40l50_dapm_routes), 272 + }; 273 + 274 + static int cs40l50_codec_driver_probe(struct platform_device *pdev) 275 + { 276 + struct cs40l50 *cs40l50 = dev_get_drvdata(pdev->dev.parent); 277 + struct cs40l50_codec *codec; 278 + 279 + codec = devm_kzalloc(&pdev->dev, sizeof(*codec), GFP_KERNEL); 280 + if (!codec) 281 + return -ENOMEM; 282 + 283 + codec->regmap = cs40l50->regmap; 284 + codec->dev = &pdev->dev; 285 + 286 + return devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_cs40l50, 287 + cs40l50_dai, ARRAY_SIZE(cs40l50_dai)); 288 + } 289 + 290 + static const struct platform_device_id cs40l50_id[] = { 291 + { "cs40l50-codec", }, 292 + {} 293 + }; 294 + MODULE_DEVICE_TABLE(platform, cs40l50_id); 295 + 296 + static struct platform_driver cs40l50_codec_driver = { 297 + .probe = cs40l50_codec_driver_probe, 298 + .id_table = cs40l50_id, 299 + .driver = { 300 + .name = "cs40l50-codec", 301 + }, 302 + }; 303 + module_platform_driver(cs40l50_codec_driver); 304 + 305 + MODULE_DESCRIPTION("ASoC CS40L50 driver"); 306 + MODULE_AUTHOR("James Ogletree <james.ogletree@cirrus.com>"); 307 + MODULE_LICENSE("GPL");