···11+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause22+%YAML 1.233+---44+$id: http://devicetree.org/schemas/input/cirrus,cs40l50.yaml#55+$schema: http://devicetree.org/meta-schemas/core.yaml#66+77+title: Cirrus Logic CS40L50 Advanced Haptic Driver88+99+maintainers:1010+ - James Ogletree <jogletre@opensource.cirrus.com>1111+1212+description:1313+ CS40L50 is a haptic driver with waveform memory,1414+ integrated DSP, and closed-loop algorithms.1515+1616+properties:1717+ compatible:1818+ enum:1919+ - cirrus,cs40l502020+2121+ reg:2222+ maxItems: 12323+2424+ interrupts:2525+ maxItems: 12626+2727+ reset-gpios:2828+ maxItems: 12929+3030+ vdd-a-supply:3131+ description: Power supply for internal analog circuits.3232+3333+ vdd-p-supply:3434+ description: Power supply for always-on circuits.3535+3636+ vdd-io-supply:3737+ description: Power supply for digital input/output.3838+3939+ vdd-b-supply:4040+ description: Power supply for the boost converter.4141+4242+required:4343+ - compatible4444+ - reg4545+ - interrupts4646+ - reset-gpios4747+ - vdd-io-supply4848+4949+additionalProperties: false5050+5151+examples:5252+ - |5353+ #include <dt-bindings/gpio/gpio.h>5454+ #include <dt-bindings/interrupt-controller/irq.h>5555+5656+ i2c {5757+ #address-cells = <1>;5858+ #size-cells = <0>;5959+6060+ haptic-driver@34 {6161+ compatible = "cirrus,cs40l50";6262+ reg = <0x34>;6363+ interrupt-parent = <&gpio>;6464+ interrupts = <113 IRQ_TYPE_LEVEL_LOW>;6565+ reset-gpios = <&gpio 112 GPIO_ACTIVE_LOW>;6666+ vdd-io-supply = <&vreg>;6767+ };6868+ };
+12
MAINTAINERS
···52115211F: sound/pci/hda/hda_cs_dsp_ctl.*52125212F: sound/soc/codecs/cs*5213521352145214+CIRRUS LOGIC HAPTIC DRIVERS52155215+M: James Ogletree <jogletre@opensource.cirrus.com>52165216+M: Fred Treven <fred.treven@cirrus.com>52175217+M: Ben Bright <ben.bright@cirrus.com>52185218+L: patches@opensource.cirrus.com52195219+S: Supported52205220+F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml52215221+F: drivers/input/misc/cs40l*52225222+F: drivers/mfd/cs40l*52235223+F: include/linux/mfd/cs40l*52245224+F: sound/soc/codecs/cs40l*52255225+52145226CIRRUS LOGIC DSP FIRMWARE DRIVER52155227M: Simon Trimmer <simont@opensource.cirrus.com>52165228M: Charles Keepax <ckeepax@opensource.cirrus.com>
+278
drivers/firmware/cirrus/cs_dsp.c
···275275#define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff276276#define HALO_MPU_VIO_ERR_SRC_SHIFT 0277277278278+/*279279+ * Write Sequence280280+ */281281+#define WSEQ_OP_MAX_WORDS 3282282+#define WSEQ_END_OF_SCRIPT 0xFFFFFF283283+278284struct cs_dsp_ops {279285 bool (*validate_version)(struct cs_dsp *dsp, unsigned int version);280286 unsigned int (*parse_sizes)(struct cs_dsp *dsp,···34033397 return result;34043398}34053399EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_read, FW_CS_DSP);34003400+34013401+34023402+struct cs_dsp_wseq_op {34033403+ struct list_head list;34043404+ u32 address;34053405+ u32 data;34063406+ u16 offset;34073407+ u8 operation;34083408+};34093409+34103410+static void cs_dsp_wseq_clear(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq)34113411+{34123412+ struct cs_dsp_wseq_op *op, *op_tmp;34133413+34143414+ list_for_each_entry_safe(op, op_tmp, &wseq->ops, list) {34153415+ list_del(&op->list);34163416+ devm_kfree(dsp->dev, op);34173417+ }34183418+}34193419+34203420+static int cs_dsp_populate_wseq(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq)34213421+{34223422+ struct cs_dsp_wseq_op *op = NULL;34233423+ struct cs_dsp_chunk chunk;34243424+ u8 *words;34253425+ int ret;34263426+34273427+ if (!wseq->ctl) {34283428+ cs_dsp_err(dsp, "No control for write sequence\n");34293429+ return -EINVAL;34303430+ }34313431+34323432+ words = kzalloc(wseq->ctl->len, GFP_KERNEL);34333433+ if (!words)34343434+ return -ENOMEM;34353435+34363436+ ret = cs_dsp_coeff_read_ctrl(wseq->ctl, 0, words, wseq->ctl->len);34373437+ if (ret) {34383438+ cs_dsp_err(dsp, "Failed to read %s: %d\n", wseq->ctl->subname, ret);34393439+ goto err_free;34403440+ }34413441+34423442+ INIT_LIST_HEAD(&wseq->ops);34433443+34443444+ chunk = cs_dsp_chunk(words, wseq->ctl->len);34453445+34463446+ while (!cs_dsp_chunk_end(&chunk)) {34473447+ op = devm_kzalloc(dsp->dev, sizeof(*op), GFP_KERNEL);34483448+ if (!op) {34493449+ ret = -ENOMEM;34503450+ goto err_free;34513451+ }34523452+34533453+ op->offset = cs_dsp_chunk_bytes(&chunk);34543454+ op->operation = cs_dsp_chunk_read(&chunk, 8);34553455+34563456+ switch (op->operation) {34573457+ case CS_DSP_WSEQ_END:34583458+ op->data = WSEQ_END_OF_SCRIPT;34593459+ break;34603460+ case CS_DSP_WSEQ_UNLOCK:34613461+ op->data = cs_dsp_chunk_read(&chunk, 16);34623462+ break;34633463+ case CS_DSP_WSEQ_ADDR8:34643464+ op->address = cs_dsp_chunk_read(&chunk, 8);34653465+ op->data = cs_dsp_chunk_read(&chunk, 32);34663466+ break;34673467+ case CS_DSP_WSEQ_H16:34683468+ case CS_DSP_WSEQ_L16:34693469+ op->address = cs_dsp_chunk_read(&chunk, 24);34703470+ op->data = cs_dsp_chunk_read(&chunk, 16);34713471+ break;34723472+ case CS_DSP_WSEQ_FULL:34733473+ op->address = cs_dsp_chunk_read(&chunk, 32);34743474+ op->data = cs_dsp_chunk_read(&chunk, 32);34753475+ break;34763476+ default:34773477+ ret = -EINVAL;34783478+ cs_dsp_err(dsp, "Unsupported op: %X\n", op->operation);34793479+ devm_kfree(dsp->dev, op);34803480+ goto err_free;34813481+ }34823482+34833483+ list_add_tail(&op->list, &wseq->ops);34843484+34853485+ if (op->operation == CS_DSP_WSEQ_END)34863486+ break;34873487+ }34883488+34893489+ if (op && op->operation != CS_DSP_WSEQ_END) {34903490+ cs_dsp_err(dsp, "%s missing end terminator\n", wseq->ctl->subname);34913491+ ret = -ENOENT;34923492+ }34933493+34943494+err_free:34953495+ kfree(words);34963496+34973497+ return ret;34983498+}34993499+35003500+/**35013501+ * cs_dsp_wseq_init() - Initialize write sequences contained within the loaded DSP firmware35023502+ * @dsp: Pointer to DSP structure35033503+ * @wseqs: List of write sequences to initialize35043504+ * @num_wseqs: Number of write sequences to initialize35053505+ *35063506+ * Return: Zero for success, a negative number on error.35073507+ */35083508+int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs)35093509+{35103510+ int i, ret;35113511+35123512+ lockdep_assert_held(&dsp->pwr_lock);35133513+35143514+ for (i = 0; i < num_wseqs; i++) {35153515+ ret = cs_dsp_populate_wseq(dsp, &wseqs[i]);35163516+ if (ret) {35173517+ cs_dsp_wseq_clear(dsp, &wseqs[i]);35183518+ return ret;35193519+ }35203520+ }35213521+35223522+ return 0;35233523+}35243524+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_init, FW_CS_DSP);35253525+35263526+static struct cs_dsp_wseq_op *cs_dsp_wseq_find_op(u32 addr, u8 op_code,35273527+ struct list_head *wseq_ops)35283528+{35293529+ struct cs_dsp_wseq_op *op;35303530+35313531+ list_for_each_entry(op, wseq_ops, list) {35323532+ if (op->operation == op_code && op->address == addr)35333533+ return op;35343534+ }35353535+35363536+ return NULL;35373537+}35383538+35393539+/**35403540+ * cs_dsp_wseq_write() - Add or update an entry in a write sequence35413541+ * @dsp: Pointer to a DSP structure35423542+ * @wseq: Write sequence to write to35433543+ * @addr: Address of the register to be written to35443544+ * @data: Data to be written35453545+ * @op_code: The type of operation of the new entry35463546+ * @update: If true, searches for the first entry in the write sequence with35473547+ * the same address and op_code, and replaces it. If false, creates a new entry35483548+ * at the tail35493549+ *35503550+ * This function formats register address and value pairs into the format35513551+ * required for write sequence entries, and either updates or adds the35523552+ * new entry into the write sequence.35533553+ *35543554+ * If update is set to true and no matching entry is found, it will add a new entry.35553555+ *35563556+ * Return: Zero for success, a negative number on error.35573557+ */35583558+int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,35593559+ u32 addr, u32 data, u8 op_code, bool update)35603560+{35613561+ struct cs_dsp_wseq_op *op_end, *op_new = NULL;35623562+ u32 words[WSEQ_OP_MAX_WORDS];35633563+ struct cs_dsp_chunk chunk;35643564+ int new_op_size, ret;35653565+35663566+ if (update)35673567+ op_new = cs_dsp_wseq_find_op(addr, op_code, &wseq->ops);35683568+35693569+ /* If entry to update is not found, treat it as a new operation */35703570+ if (!op_new) {35713571+ op_end = cs_dsp_wseq_find_op(0, CS_DSP_WSEQ_END, &wseq->ops);35723572+ if (!op_end) {35733573+ cs_dsp_err(dsp, "Missing terminator for %s\n", wseq->ctl->subname);35743574+ return -EINVAL;35753575+ }35763576+35773577+ op_new = devm_kzalloc(dsp->dev, sizeof(*op_new), GFP_KERNEL);35783578+ if (!op_new)35793579+ return -ENOMEM;35803580+35813581+ op_new->operation = op_code;35823582+ op_new->address = addr;35833583+ op_new->offset = op_end->offset;35843584+ update = false;35853585+ }35863586+35873587+ op_new->data = data;35883588+35893589+ chunk = cs_dsp_chunk(words, sizeof(words));35903590+ cs_dsp_chunk_write(&chunk, 8, op_new->operation);35913591+35923592+ switch (op_code) {35933593+ case CS_DSP_WSEQ_FULL:35943594+ cs_dsp_chunk_write(&chunk, 32, op_new->address);35953595+ cs_dsp_chunk_write(&chunk, 32, op_new->data);35963596+ break;35973597+ case CS_DSP_WSEQ_L16:35983598+ case CS_DSP_WSEQ_H16:35993599+ cs_dsp_chunk_write(&chunk, 24, op_new->address);36003600+ cs_dsp_chunk_write(&chunk, 16, op_new->data);36013601+ break;36023602+ default:36033603+ ret = -EINVAL;36043604+ cs_dsp_err(dsp, "Operation %X not supported\n", op_code);36053605+ goto op_new_free;36063606+ }36073607+36083608+ new_op_size = cs_dsp_chunk_bytes(&chunk);36093609+36103610+ if (!update) {36113611+ if (wseq->ctl->len - op_end->offset < new_op_size) {36123612+ cs_dsp_err(dsp, "Not enough memory in %s for entry\n", wseq->ctl->subname);36133613+ ret = -E2BIG;36143614+ goto op_new_free;36153615+ }36163616+36173617+ op_end->offset += new_op_size;36183618+36193619+ ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_end->offset / sizeof(u32),36203620+ &op_end->data, sizeof(u32));36213621+ if (ret)36223622+ goto op_new_free;36233623+36243624+ list_add_tail(&op_new->list, &op_end->list);36253625+ }36263626+36273627+ ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_new->offset / sizeof(u32),36283628+ words, new_op_size);36293629+ if (ret)36303630+ goto op_new_free;36313631+36323632+ return 0;36333633+36343634+op_new_free:36353635+ devm_kfree(dsp->dev, op_new);36363636+36373637+ return ret;36383638+}36393639+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_write, FW_CS_DSP);36403640+36413641+/**36423642+ * cs_dsp_wseq_multi_write() - Add or update multiple entries in a write sequence36433643+ * @dsp: Pointer to a DSP structure36443644+ * @wseq: Write sequence to write to36453645+ * @reg_seq: List of address-data pairs36463646+ * @num_regs: Number of address-data pairs36473647+ * @op_code: The types of operations of the new entries36483648+ * @update: If true, searches for the first entry in the write sequence with36493649+ * the same address and op_code, and replaces it. If false, creates a new entry36503650+ * at the tail36513651+ *36523652+ * This function calls cs_dsp_wseq_write() for multiple address-data pairs.36533653+ *36543654+ * Return: Zero for success, a negative number on error.36553655+ */36563656+int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,36573657+ const struct reg_sequence *reg_seq, int num_regs,36583658+ u8 op_code, bool update)36593659+{36603660+ int i, ret;36613661+36623662+ for (i = 0; i < num_regs; i++) {36633663+ ret = cs_dsp_wseq_write(dsp, wseq, reg_seq[i].reg,36643664+ reg_seq[i].def, op_code, update);36653665+ if (ret)36663666+ return ret;36673667+ }36683668+36693669+ return 0;36703670+}36713671+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_multi_write, FW_CS_DSP);3406367234073673MODULE_DESCRIPTION("Cirrus Logic DSP Support");34083674MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+10
drivers/input/misc/Kconfig
···140140 To compile this driver as a module, choose M here: the141141 module will be called bma150.142142143143+config INPUT_CS40L50_VIBRA144144+ tristate "CS40L50 Haptic Driver support"145145+ depends on MFD_CS40L50_CORE146146+ help147147+ Say Y here to enable support for Cirrus Logic's CS40L50148148+ haptic driver.149149+150150+ To compile this driver as a module, choose M here: the151151+ module will be called cs40l50-vibra.152152+143153config INPUT_E3X0_BUTTON144154 tristate "NI Ettus Research USRP E3xx Button support."145155 default n
···2243224322442244endmenu2245224522462246+config MFD_CS40L50_CORE22472247+ tristate22482248+ select MFD_CORE22492249+ select FW_CS_DSP22502250+ select REGMAP_IRQ22512251+22522252+config MFD_CS40L50_I2C22532253+ tristate "Cirrus Logic CS40L50 (I2C)"22542254+ select REGMAP_I2C22552255+ select MFD_CS40L50_CORE22562256+ depends on I2C22572257+ help22582258+ Select this to support the Cirrus Logic CS40L50 Haptic22592259+ Driver over I2C.22602260+22612261+ This driver can be built as a module. If built as a module it will be22622262+ called "cs40l50-i2c".22632263+22642264+config MFD_CS40L50_SPI22652265+ tristate "Cirrus Logic CS40L50 (SPI)"22662266+ select REGMAP_SPI22672267+ select MFD_CS40L50_CORE22682268+ depends on SPI22692269+ help22702270+ Select this to support the Cirrus Logic CS40L50 Haptic22712271+ Driver over SPI.22722272+22732273+ This driver can be built as a module. If built as a module it will be22742274+ called "cs40l50-spi".22752275+22462276config MFD_VEXPRESS_SYSREG22472277 tristate "Versatile Express System Registers"22482278 depends on VEXPRESS_CONFIG && GPIOLIB
···7575 imply SND_SOC_CS35L56_I2C7676 imply SND_SOC_CS35L56_SPI7777 imply SND_SOC_CS35L56_SDW7878+ imply SND_SOC_CS40L507879 imply SND_SOC_CS42L427980 imply SND_SOC_CS42L42_SDW8081 imply SND_SOC_CS42L43···847846 select SND_SOC_CS35L56_SHARED848847 help849848 Enable support for Cirrus Logic CS35L56 boosted amplifier with SoundWire control849849+850850+config SND_SOC_CS40L50851851+ tristate "Cirrus Logic CS40L50 CODEC"852852+ depends on MFD_CS40L50_CORE853853+ help854854+ This option enables support for I2S streaming to Cirrus Logic CS40L50.855855+856856+ CS40L50 is a haptic driver with waveform memory, an integrated857857+ DSP, and closed-loop algorithms. If built as a module, it will be858858+ called snd-soc-cs40l50.850859851860config SND_SOC_CS42L42_CORE852861 tristate