diff mbox series

[v4,4/4] Input: cs40l50 - Add support for the CS40L50 haptic driver

Message ID 20231018175726.3879955-5-james.ogletree@opensource.cirrus.com (mailing list archive)
State New
Headers show
Series Add support for CS40L50 | expand

Commit Message

James Ogletree Oct. 18, 2023, 5:57 p.m. UTC
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

Comments

kernel test robot Oct. 20, 2023, 3:30 p.m. UTC | #1
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
Jeff LaBundy Oct. 25, 2023, 3:03 a.m. UTC | #2
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
James Ogletree Nov. 1, 2023, 8:47 p.m. UTC | #3
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
Jeff LaBundy Nov. 26, 2023, 1:11 a.m. UTC | #4
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 mbox series

Patch

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");