From: James Ogletree <james.ogletree@xxxxxxxxxx> Introduce support for Cirrus Logic Device CS40L50: a haptic driver with waveform memory, integrated DSP, and closed-loop algorithms. The input driver provides the interface for control of haptic effects through the device. Signed-off-by: James Ogletree <james.ogletree@xxxxxxxxxx> --- MAINTAINERS | 1 + drivers/input/misc/Kconfig | 10 + drivers/input/misc/Makefile | 1 + drivers/input/misc/cs40l50-vibra.c | 353 +++++++++++++++++++++++++++++ 4 files changed, 365 insertions(+) create mode 100644 drivers/input/misc/cs40l50-vibra.c diff --git a/MAINTAINERS b/MAINTAINERS index 08e1e9695d49..24a00d8e5c1c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4971,6 +4971,7 @@ L: patches@xxxxxxxxxxxxxxxxxxxxx S: Supported F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml F: drivers/input/misc/cirrus* +F: drivers/input/misc/cs40l* F: drivers/mfd/cs40l* F: include/linux/input/cirrus* F: include/linux/mfd/cs40l* diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 9f088900f863..938090648126 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -129,6 +129,16 @@ config INPUT_BMA150 To compile this driver as a module, choose M here: the module will be called bma150. +config INPUT_CS40L50_VIBRA + tristate "CS40L50 Haptic Driver support" + depends on MFD_CS40L50_CORE + help + Say Y here to enable support for Cirrus Logic's CS40L50 + haptic driver. + + To compile this driver as a module, choose M here: the + module will be called cs40l50-vibra. + config INPUT_E3X0_BUTTON tristate "NI Ettus Research USRP E3xx Button support." default n diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 6abefc41037b..6b653ed2124f 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_INPUT_CMA3000) += cma3000_d0x.o obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o obj-$(CONFIG_INPUT_CPCAP_PWRBUTTON) += cpcap-pwrbutton.o +obj-$(CONFIG_INPUT_CS40L50_VIBRA) += cs40l50-vibra.o cirrus_haptics.o obj-$(CONFIG_INPUT_DA7280_HAPTICS) += da7280.o obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o diff --git a/drivers/input/misc/cs40l50-vibra.c b/drivers/input/misc/cs40l50-vibra.c new file mode 100644 index 000000000000..3b3e4cb10de0 --- /dev/null +++ b/drivers/input/misc/cs40l50-vibra.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CS40L50 Advanced Haptic Driver with waveform memory, + * integrated DSP, and closed-loop algorithms + * + * Copyright 2023 Cirrus Logic, Inc. + * + */ + +#include <linux/firmware/cirrus/wmfw.h> +#include <linux/mfd/cs40l50.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +static int cs40l50_add(struct input_dev *dev, + struct ff_effect *effect, + struct ff_effect *old) +{ + struct cs40l50_private *cs40l50 = input_get_drvdata(dev); + u32 len = effect->u.periodic.custom_len; + + if (effect->type != FF_PERIODIC || effect->u.periodic.waveform != FF_CUSTOM) { + dev_err(cs40l50->dev, "Type (%#X) or waveform (%#X) unsupported\n", + effect->type, effect->u.periodic.waveform); + return -EINVAL; + } + + memcpy(&cs40l50->haptics.add_effect, effect, sizeof(struct ff_effect)); + + cs40l50->haptics.add_effect.u.periodic.custom_data = kcalloc(len, + sizeof(s16), + GFP_KERNEL); + if (!cs40l50->haptics.add_effect.u.periodic.custom_data) + return -ENOMEM; + + if (copy_from_user(cs40l50->haptics.add_effect.u.periodic.custom_data, + effect->u.periodic.custom_data, sizeof(s16) * len)) { + cs40l50->haptics.add_error = -EFAULT; + goto out_free; + } + + queue_work(cs40l50->haptics.vibe_wq, &cs40l50->haptics.add_work); + flush_work(&cs40l50->haptics.add_work); + +out_free: + kfree(cs40l50->haptics.add_effect.u.periodic.custom_data); + cs40l50->haptics.add_effect.u.periodic.custom_data = NULL; + + return cs40l50->haptics.add_error; +} + +static int cs40l50_playback(struct input_dev *dev, int effect_id, int val) +{ + struct cs40l50_private *cs40l50 = input_get_drvdata(dev); + + if (val > 0) { + cs40l50->haptics.start_effect = &dev->ff->effects[effect_id]; + queue_work(cs40l50->haptics.vibe_wq, + &cs40l50->haptics.vibe_start_work); + } else { + queue_work(cs40l50->haptics.vibe_wq, + &cs40l50->haptics.vibe_stop_work); + } + + return 0; +} + +static int cs40l50_erase(struct input_dev *dev, int effect_id) +{ + struct cs40l50_private *cs40l50 = input_get_drvdata(dev); + + cs40l50->haptics.erase_effect = &dev->ff->effects[effect_id]; + + queue_work(cs40l50->haptics.vibe_wq, &cs40l50->haptics.erase_work); + flush_work(&cs40l50->haptics.erase_work); + + return cs40l50->haptics.erase_error; +} + +static const struct reg_sequence cs40l50_int_vamp_seq[] = { + { CS40L50_BST_LPMODE_SEL, CS40L50_DCM_LOW_POWER }, + { CS40L50_BLOCK_ENABLES2, CS40L50_OVERTEMP_WARN }, +}; + +static const struct reg_sequence cs40l50_irq_mask_seq[] = { + { CS40L50_IRQ1_MASK_2, CS40L50_IRQ_MASK_2_OVERRIDE }, + { CS40L50_IRQ1_MASK_20, CS40L50_IRQ_MASK_20_OVERRIDE }, +}; + +static int cs40l50_hw_init(struct cs40l50_private *cs40l50) +{ + int error; + + error = regmap_multi_reg_write(cs40l50->regmap, + cs40l50_int_vamp_seq, + ARRAY_SIZE(cs40l50_int_vamp_seq)); + if (error) + return error; + + error = cs_hap_pseq_multi_write(&cs40l50->haptics, + cs40l50_int_vamp_seq, + ARRAY_SIZE(cs40l50_int_vamp_seq), + false, PSEQ_OP_WRITE_FULL); + if (error) + return error; + + error = regmap_multi_reg_write(cs40l50->regmap, cs40l50_irq_mask_seq, + ARRAY_SIZE(cs40l50_irq_mask_seq)); + if (error) + return error; + + return cs_hap_pseq_multi_write(&cs40l50->haptics, cs40l50_irq_mask_seq, + ARRAY_SIZE(cs40l50_irq_mask_seq), false, + PSEQ_OP_WRITE_FULL); +} + +static const struct cs_dsp_client_ops cs40l50_cs_dsp_client_ops; + +static const struct cs_dsp_region cs40l50_dsp_regions[] = { + { + .type = WMFW_HALO_PM_PACKED, + .base = CS40L50_DSP1_PMEM_0 + }, + { + .type = WMFW_HALO_XM_PACKED, + .base = CS40L50_DSP1_XMEM_PACKED_0 + }, + { + .type = WMFW_HALO_YM_PACKED, + .base = CS40L50_DSP1_YMEM_PACKED_0 + }, + { + .type = WMFW_ADSP2_XM, + .base = CS40L50_DSP1_XMEM_UNPACKED24_0 + }, + { + .type = WMFW_ADSP2_YM, + .base = CS40L50_DSP1_YMEM_UNPACKED24_0 + }, +}; + +static int cs40l50_cs_dsp_init(struct cs40l50_private *cs40l50) +{ + cs40l50->dsp.num = 1; + cs40l50->dsp.type = WMFW_HALO; + cs40l50->dsp.dev = cs40l50->dev; + cs40l50->dsp.regmap = cs40l50->regmap; + cs40l50->dsp.base = CS40L50_CORE_BASE; + cs40l50->dsp.base_sysinfo = CS40L50_SYS_INFO_ID; + cs40l50->dsp.mem = cs40l50_dsp_regions; + cs40l50->dsp.num_mems = ARRAY_SIZE(cs40l50_dsp_regions); + cs40l50->dsp.no_core_startstop = true; + cs40l50->dsp.client_ops = &cs40l50_cs_dsp_client_ops; + + return cs_dsp_halo_init(&cs40l50->dsp); +} + +static struct cs_hap_bank cs40l50_banks[] = { + { + .bank = WVFRM_BANK_RAM, + .base_index = CS40L50_RAM_BANK_INDEX_START, + .max_index = CS40L50_RAM_BANK_INDEX_START, + }, + { + .bank = WVFRM_BANK_ROM, + .base_index = CS40L50_ROM_BANK_INDEX_START, + .max_index = CS40L50_ROM_BANK_INDEX_END, + }, + { + .bank = WVFRM_BANK_OWT, + .base_index = CS40L50_RTH_INDEX_START, + .max_index = CS40L50_RTH_INDEX_END, + }, +}; + +static int cs40l50_cs_hap_init(struct cs40l50_private *cs40l50) +{ + cs40l50->haptics.regmap = cs40l50->regmap; + cs40l50->haptics.dev = cs40l50->dev; + cs40l50->haptics.banks = cs40l50_banks; + cs40l50->haptics.dsp.gpio_base_reg = CS40L50_GPIO_BASE; + cs40l50->haptics.dsp.owt_base_reg = CS40L50_OWT_BASE; + cs40l50->haptics.dsp.owt_offset_reg = CS40L50_OWT_NEXT; + cs40l50->haptics.dsp.owt_size_reg = CS40L50_OWT_SIZE; + cs40l50->haptics.dsp.mailbox_reg = CS40L50_DSP_MBOX; + cs40l50->haptics.dsp.pseq_reg = CS40L50_POWER_ON_SEQ; + cs40l50->haptics.dsp.push_owt_cmd = CS40L50_OWT_PUSH; + cs40l50->haptics.dsp.delete_owt_cmd = CS40L50_OWT_DELETE; + cs40l50->haptics.dsp.stop_cmd = CS40L50_STOP_PLAYBACK; + cs40l50->haptics.dsp.pseq_size = CS40L50_PSEQ_SIZE; + cs40l50->haptics.runtime_pm = true; + + return cs_hap_init(&cs40l50->haptics); +} + +static void cs40l50_upload_wt(const struct firmware *bin, void *context) +{ + struct cs40l50_private *cs40l50 = context; + u32 nwaves; + + mutex_lock(&cs40l50->lock); + + if (cs40l50->wmfw) { + if (regmap_write(cs40l50->regmap, CS40L50_CCM_CORE_CONTROL, + CS40L50_CLOCK_DISABLE)) + goto err_mutex; + + if (regmap_write(cs40l50->regmap, CS40L50_RAM_INIT, + CS40L50_RAM_INIT_FLAG)) + goto err_mutex; + + if (regmap_write(cs40l50->regmap, CS40L50_PWRMGT_CTL, + CS40L50_MEM_RDY_HW)) + goto err_mutex; + } + + cs_dsp_power_up(&cs40l50->dsp, cs40l50->wmfw, "cs40l50.wmfw", + bin, "cs40l50.bin", "cs40l50"); + + if (cs40l50->wmfw) { + if (regmap_write(cs40l50->regmap, CS40L50_CCM_CORE_CONTROL, + CS40L50_CLOCK_ENABLE)) + goto err_mutex; + } + + if (regmap_read(cs40l50->regmap, CS40L50_NUM_OF_WAVES, &nwaves)) + goto err_mutex; + + cs40l50->haptics.banks[WVFRM_BANK_RAM].max_index += (nwaves - 1); + +err_mutex: + mutex_unlock(&cs40l50->lock); + release_firmware(bin); + release_firmware(cs40l50->wmfw); +} + +static void cs40l50_upload_patch(const struct firmware *wmfw, void *context) +{ + struct cs40l50_private *cs40l50 = context; + + cs40l50->wmfw = wmfw; + + if (request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, CS40L50_WT, + cs40l50->dev, GFP_KERNEL, + cs40l50, cs40l50_upload_wt)) + release_firmware(cs40l50->wmfw); +} + +static int cs40l50_input_init(struct cs40l50_private *cs40l50) +{ + int error; + + cs40l50->input = devm_input_allocate_device(cs40l50->dev); + if (!cs40l50->input) + return -ENOMEM; + + cs40l50->input->id.product = cs40l50->devid & 0xFFFF; + cs40l50->input->id.version = cs40l50->revid; + cs40l50->input->name = "cs40l50_vibra"; + + input_set_drvdata(cs40l50->input, cs40l50); + input_set_capability(cs40l50->input, EV_FF, FF_PERIODIC); + input_set_capability(cs40l50->input, EV_FF, FF_CUSTOM); + + error = input_ff_create(cs40l50->input, FF_MAX_EFFECTS); + if (error) + return error; + + cs40l50->input->ff->upload = cs40l50_add; + cs40l50->input->ff->playback = cs40l50_playback; + cs40l50->input->ff->erase = cs40l50_erase; + + INIT_LIST_HEAD(&cs40l50->haptics.effect_head); + + error = input_register_device(cs40l50->input); + if (error) + goto err_free_dev; + + return cs40l50_cs_hap_init(cs40l50); + +err_free_dev: + input_free_device(cs40l50->input); + return error; +} +static int cs40l50_vibra_probe(struct platform_device *pdev) +{ + struct cs40l50_private *cs40l50 = dev_get_drvdata(pdev->dev.parent); + int error; + + error = cs40l50_input_init(cs40l50); + if (error) + return error; + + error = cs40l50_hw_init(cs40l50); + if (error) + goto err_input; + + error = cs40l50_cs_dsp_init(cs40l50); + if (error) + goto err_input; + + error = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, + CS40L50_FW, cs40l50->dev, + GFP_KERNEL, cs40l50, + cs40l50_upload_patch); + if (error) + goto err_dsp; + + return 0; + +err_dsp: + cs_dsp_remove(&cs40l50->dsp); +err_input: + input_unregister_device(cs40l50->input); + cs_hap_remove(&cs40l50->haptics); + + return error; +} + +static int cs40l50_vibra_remove(struct platform_device *pdev) +{ + struct cs40l50_private *cs40l50 = dev_get_drvdata(pdev->dev.parent); + + input_unregister_device(cs40l50->input); + cs_hap_remove(&cs40l50->haptics); + + if (cs40l50->dsp.booted) + cs_dsp_power_down(&cs40l50->dsp); + if (&cs40l50->dsp) + cs_dsp_remove(&cs40l50->dsp); + + return 0; +} + +static const struct platform_device_id cs40l50_id_vibra[] = { + {"cs40l50-vibra", }, + {} +}; +MODULE_DEVICE_TABLE(platform, cs40l50_id_vibra); + +static struct platform_driver cs40l50_vibra_driver = { + .probe = cs40l50_vibra_probe, + .remove = cs40l50_vibra_remove, + .id_table = cs40l50_id_vibra, + .driver = { + .name = "cs40l50-vibra", + }, +}; +module_platform_driver(cs40l50_vibra_driver); + +MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver"); +MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@xxxxxxxxxx>"); +MODULE_LICENSE("GPL"); -- 2.25.1