Message ID | 20231018175726.3879955-5-james.ogletree@opensource.cirrus.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | Add support for CS40L50 | expand |
Hi James, kernel test robot noticed the following build warnings: [auto build test WARNING on dtor-input/next] [also build test WARNING on dtor-input/for-linus lee-mfd/for-mfd-next robh/for-next linus/master lee-mfd/for-mfd-fixes v6.6-rc6 next-20231020] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/James-Ogletree/dt-bindings-input-cirrus-cs40l50-Add-initial-DT-binding/20231019-015950 base: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next patch link: https://lore.kernel.org/r/20231018175726.3879955-5-james.ogletree%40opensource.cirrus.com patch subject: [PATCH v4 4/4] Input: cs40l50 - Add support for the CS40L50 haptic driver config: sparc-allyesconfig (https://download.01.org/0day-ci/archive/20231020/202310202344.LreohGFP-lkp@intel.com/config) compiler: sparc64-linux-gcc (GCC) 13.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231020/202310202344.LreohGFP-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202310202344.LreohGFP-lkp@intel.com/ All warnings (new ones prefixed by >>): drivers/input/misc/cs40l50-vibra.c: In function 'cs40l50_vibra_remove': >> drivers/input/misc/cs40l50-vibra.c:329:13: warning: the comparison will always evaluate as 'true' for the address of 'dsp' will never be NULL [-Waddress] 329 | if (&cs40l50->dsp) | ^ In file included from drivers/input/misc/cs40l50-vibra.c:11: include/linux/mfd/cs40l50.h:180:23: note: 'dsp' declared here 180 | struct cs_dsp dsp; | ^~~ vim +329 drivers/input/misc/cs40l50-vibra.c 319 320 static int cs40l50_vibra_remove(struct platform_device *pdev) 321 { 322 struct cs40l50_private *cs40l50 = dev_get_drvdata(pdev->dev.parent); 323 324 input_unregister_device(cs40l50->input); 325 cs_hap_remove(&cs40l50->haptics); 326 327 if (cs40l50->dsp.booted) 328 cs_dsp_power_down(&cs40l50->dsp); > 329 if (&cs40l50->dsp) 330 cs_dsp_remove(&cs40l50->dsp); 331 332 return 0; 333 } 334
Hi James, On Wed, Oct 18, 2023 at 05:57:25PM +0000, James Ogletree wrote: > From: James Ogletree <james.ogletree@cirrus.com> > > 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@cirrus.com> > --- > 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@opensource.cirrus.com > 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, > + }, > +}; These structs describe the DSP, and hence the silicon; they are not specific to the input/FF device. Presumably the DSP could run algorithms that support only the I2S streaming case as well (e.g. A2H); therefore, these seem more appropriately placed in the MFD. > + > +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@cirrus.com>"); > +MODULE_LICENSE("GPL"); > -- > 2.25.1 > Kind regards, Jeff LaBundy
Hi Jeff, > On Oct 24, 2023, at 10:03 PM, Jeff LaBundy <jeff@labundy.com> wrote: >> >> +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, >> + }, >> +}; > > These structs describe the DSP, and hence the silicon; they are not > specific to the input/FF device. Presumably the DSP could run algorithms > that support only the I2S streaming case as well (e.g. A2H); therefore, > these seem more appropriately placed in the MFD. Acknowledged, but would you consider the last struct “cs40l50_banks” as an exception? It would go unused in a codec-only setup. Best, James
Hi James, On Wed, Nov 01, 2023 at 08:47:07PM +0000, James Ogletree wrote: > Hi Jeff, > > > On Oct 24, 2023, at 10:03 PM, Jeff LaBundy <jeff@labundy.com> wrote: > >> > >> +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, > >> + }, > >> +}; > > > > These structs describe the DSP, and hence the silicon; they are not > > specific to the input/FF device. Presumably the DSP could run algorithms > > that support only the I2S streaming case as well (e.g. A2H); therefore, > > these seem more appropriately placed in the MFD. > > Acknowledged, but would you consider the last struct “cs40l50_banks” as > an exception? It would go unused in a codec-only setup. I agree with you; I should have inserted my comment after cs40l50_cs_dsp_init() and not cs40l50_banks[]. > > Best, > James > > Kind regards, Jeff LaBundy
diff --git a/MAINTAINERS b/MAINTAINERS index 08e1e9695d49..24a00d8e5c1c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4971,6 +4971,7 @@ L: patches@opensource.cirrus.com 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@cirrus.com>"); +MODULE_LICENSE("GPL");