| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * CS40L50 Advanced Haptic Driver with waveform memory,
- * integrated DSP, and closed-loop algorithms
- *
- * Copyright 2024 Cirrus Logic, Inc.
- *
- * Author: James Ogletree <james.ogletree@cirrus.com>
- */
- #include <linux/bitfield.h>
- #include <linux/input.h>
- #include <linux/mfd/cs40l50.h>
- #include <linux/platform_device.h>
- #include <linux/pm_runtime.h>
- /* Wavetables */
- #define CS40L50_RAM_INDEX_START 0x1000000
- #define CS40L50_RAM_INDEX_END 0x100007F
- #define CS40L50_RTH_INDEX_START 0x1400000
- #define CS40L50_RTH_INDEX_END 0x1400001
- #define CS40L50_ROM_INDEX_START 0x1800000
- #define CS40L50_ROM_INDEX_END 0x180001A
- #define CS40L50_TYPE_PCM 8
- #define CS40L50_TYPE_PWLE 12
- #define CS40L50_PCM_ID 0x0
- #define CS40L50_OWT_CUSTOM_DATA_SIZE 2
- #define CS40L50_CUSTOM_DATA_MASK 0xFFFFU
- /* DSP */
- #define CS40L50_GPIO_BASE 0x2804140
- #define CS40L50_OWT_BASE 0x2805C34
- #define CS40L50_OWT_SIZE 0x2805C38
- #define CS40L50_OWT_NEXT 0x2805C3C
- #define CS40L50_EFFECTS_MAX 1
- /* GPIO */
- #define CS40L50_GPIO_NUM_MASK GENMASK(14, 12)
- #define CS40L50_GPIO_EDGE_MASK BIT(15)
- #define CS40L50_GPIO_MAPPING_NONE 0
- #define CS40L50_GPIO_DISABLE 0x1FF
- enum cs40l50_bank_type {
- CS40L50_WVFRM_BANK_RAM,
- CS40L50_WVFRM_BANK_ROM,
- CS40L50_WVFRM_BANK_OWT,
- CS40L50_WVFRM_BANK_NUM,
- };
- /* Describes an area in DSP memory populated by effects */
- struct cs40l50_bank {
- enum cs40l50_bank_type type;
- u32 base_index;
- u32 max_index;
- };
- struct cs40l50_effect {
- enum cs40l50_bank_type type;
- struct list_head list;
- u32 gpio_reg;
- u32 index;
- int id;
- };
- /* Describes haptic interface of loaded DSP firmware */
- struct cs40l50_vibra_dsp {
- struct cs40l50_bank *banks;
- u32 gpio_base_reg;
- u32 owt_offset_reg;
- u32 owt_size_reg;
- u32 owt_base_reg;
- u32 push_owt_cmd;
- u32 delete_owt_cmd;
- u32 stop_cmd;
- int (*write)(struct device *dev, struct regmap *regmap, u32 val);
- };
- /* Describes configuration and state of haptic operations */
- struct cs40l50_vibra {
- struct device *dev;
- struct regmap *regmap;
- struct input_dev *input;
- struct workqueue_struct *vib_wq;
- struct list_head effect_head;
- struct cs40l50_vibra_dsp dsp;
- };
- struct cs40l50_work {
- struct cs40l50_vibra *vib;
- struct ff_effect *effect;
- struct work_struct work;
- s16 *custom_data;
- int custom_len;
- int count;
- int error;
- };
- static struct cs40l50_bank cs40l50_banks[] = {
- {
- .type = CS40L50_WVFRM_BANK_RAM,
- .base_index = CS40L50_RAM_INDEX_START,
- .max_index = CS40L50_RAM_INDEX_END,
- },
- {
- .type = CS40L50_WVFRM_BANK_ROM,
- .base_index = CS40L50_ROM_INDEX_START,
- .max_index = CS40L50_ROM_INDEX_END,
- },
- {
- .type = CS40L50_WVFRM_BANK_OWT,
- .base_index = CS40L50_RTH_INDEX_START,
- .max_index = CS40L50_RTH_INDEX_END,
- },
- };
- static struct cs40l50_vibra_dsp cs40l50_dsp = {
- .banks = cs40l50_banks,
- .gpio_base_reg = CS40L50_GPIO_BASE,
- .owt_base_reg = CS40L50_OWT_BASE,
- .owt_offset_reg = CS40L50_OWT_NEXT,
- .owt_size_reg = CS40L50_OWT_SIZE,
- .push_owt_cmd = CS40L50_OWT_PUSH,
- .delete_owt_cmd = CS40L50_OWT_DELETE,
- .stop_cmd = CS40L50_STOP_PLAYBACK,
- .write = cs40l50_dsp_write,
- };
- static struct cs40l50_effect *cs40l50_find_effect(int id, struct list_head *effect_head)
- {
- struct cs40l50_effect *effect;
- list_for_each_entry(effect, effect_head, list)
- if (effect->id == id)
- return effect;
- return NULL;
- }
- static int cs40l50_effect_bank_set(struct cs40l50_work *work_data,
- struct cs40l50_effect *effect)
- {
- s16 bank_type = work_data->custom_data[0] & CS40L50_CUSTOM_DATA_MASK;
- if (bank_type >= CS40L50_WVFRM_BANK_NUM) {
- dev_err(work_data->vib->dev, "Invalid bank (%d)\n", bank_type);
- return -EINVAL;
- }
- if (work_data->custom_len > CS40L50_OWT_CUSTOM_DATA_SIZE)
- effect->type = CS40L50_WVFRM_BANK_OWT;
- else
- effect->type = bank_type;
- return 0;
- }
- static int cs40l50_effect_index_set(struct cs40l50_work *work_data,
- struct cs40l50_effect *effect)
- {
- struct cs40l50_vibra *vib = work_data->vib;
- struct cs40l50_effect *owt_effect;
- u32 base_index, max_index;
- base_index = vib->dsp.banks[effect->type].base_index;
- max_index = vib->dsp.banks[effect->type].max_index;
- effect->index = base_index;
- switch (effect->type) {
- case CS40L50_WVFRM_BANK_OWT:
- list_for_each_entry(owt_effect, &vib->effect_head, list)
- if (owt_effect->type == CS40L50_WVFRM_BANK_OWT)
- effect->index++;
- break;
- case CS40L50_WVFRM_BANK_ROM:
- case CS40L50_WVFRM_BANK_RAM:
- effect->index += work_data->custom_data[1] & CS40L50_CUSTOM_DATA_MASK;
- break;
- default:
- dev_err(vib->dev, "Bank type %d not supported\n", effect->type);
- return -EINVAL;
- }
- if (effect->index > max_index || effect->index < base_index) {
- dev_err(vib->dev, "Index out of bounds: %u\n", effect->index);
- return -ENOSPC;
- }
- return 0;
- }
- static int cs40l50_effect_gpio_mapping_set(struct cs40l50_work *work_data,
- struct cs40l50_effect *effect)
- {
- u16 gpio_edge, gpio_num, button = work_data->effect->trigger.button;
- struct cs40l50_vibra *vib = work_data->vib;
- if (button) {
- gpio_num = FIELD_GET(CS40L50_GPIO_NUM_MASK, button);
- gpio_edge = FIELD_GET(CS40L50_GPIO_EDGE_MASK, button);
- effect->gpio_reg = vib->dsp.gpio_base_reg + (gpio_num * 8) - gpio_edge;
- return regmap_write(vib->regmap, effect->gpio_reg, button);
- }
- effect->gpio_reg = CS40L50_GPIO_MAPPING_NONE;
- return 0;
- }
- struct cs40l50_owt_header {
- u32 type;
- u32 data_words;
- u32 offset;
- } __packed;
- static int cs40l50_upload_owt(struct cs40l50_work *work_data)
- {
- u8 *new_owt_effect_data __free(kfree) = NULL;
- struct cs40l50_vibra *vib = work_data->vib;
- size_t len = work_data->custom_len * 2;
- struct cs40l50_owt_header header;
- u32 offset, size;
- int error;
- error = regmap_read(vib->regmap, vib->dsp.owt_size_reg, &size);
- if (error)
- return error;
- if ((size * sizeof(u32)) < sizeof(header) + len) {
- dev_err(vib->dev, "No space in open wavetable for effect\n");
- return -ENOSPC;
- }
- header.type = work_data->custom_data[0] == CS40L50_PCM_ID ? CS40L50_TYPE_PCM :
- CS40L50_TYPE_PWLE;
- header.offset = sizeof(header) / sizeof(u32);
- header.data_words = len / sizeof(u32);
- new_owt_effect_data = kmalloc(sizeof(header) + len, GFP_KERNEL);
- if (!new_owt_effect_data)
- return -ENOMEM;
- memcpy(new_owt_effect_data, &header, sizeof(header));
- memcpy(new_owt_effect_data + sizeof(header), work_data->custom_data, len);
- error = regmap_read(vib->regmap, vib->dsp.owt_offset_reg, &offset);
- if (error)
- return error;
- error = regmap_bulk_write(vib->regmap, vib->dsp.owt_base_reg +
- (offset * sizeof(u32)), new_owt_effect_data,
- sizeof(header) + len);
- if (error)
- return error;
- error = vib->dsp.write(vib->dev, vib->regmap, vib->dsp.push_owt_cmd);
- if (error)
- return error;
- return 0;
- }
- static void cs40l50_add_worker(struct work_struct *work)
- {
- struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
- struct cs40l50_vibra *vib = work_data->vib;
- struct cs40l50_effect *effect;
- bool is_new = false;
- int error;
- error = pm_runtime_resume_and_get(vib->dev);
- if (error)
- goto err_exit;
- /* Update effect if already uploaded, otherwise create new effect */
- effect = cs40l50_find_effect(work_data->effect->id, &vib->effect_head);
- if (!effect) {
- effect = kzalloc(sizeof(*effect), GFP_KERNEL);
- if (!effect) {
- error = -ENOMEM;
- goto err_pm;
- }
- effect->id = work_data->effect->id;
- is_new = true;
- }
- error = cs40l50_effect_bank_set(work_data, effect);
- if (error)
- goto err_free;
- error = cs40l50_effect_index_set(work_data, effect);
- if (error)
- goto err_free;
- error = cs40l50_effect_gpio_mapping_set(work_data, effect);
- if (error)
- goto err_free;
- if (effect->type == CS40L50_WVFRM_BANK_OWT)
- error = cs40l50_upload_owt(work_data);
- err_free:
- if (is_new) {
- if (error)
- kfree(effect);
- else
- list_add(&effect->list, &vib->effect_head);
- }
- err_pm:
- pm_runtime_mark_last_busy(vib->dev);
- pm_runtime_put_autosuspend(vib->dev);
- err_exit:
- work_data->error = error;
- }
- static int cs40l50_add(struct input_dev *dev, struct ff_effect *effect,
- struct ff_effect *old)
- {
- struct ff_periodic_effect *periodic = &effect->u.periodic;
- struct cs40l50_vibra *vib = input_get_drvdata(dev);
- struct cs40l50_work work_data;
- if (effect->type != FF_PERIODIC || periodic->waveform != FF_CUSTOM) {
- dev_err(vib->dev, "Type (%#X) or waveform (%#X) unsupported\n",
- effect->type, periodic->waveform);
- return -EINVAL;
- }
- work_data.custom_data = memdup_array_user(effect->u.periodic.custom_data,
- effect->u.periodic.custom_len,
- sizeof(s16));
- if (IS_ERR(work_data.custom_data))
- return PTR_ERR(work_data.custom_data);
- work_data.custom_len = effect->u.periodic.custom_len;
- work_data.vib = vib;
- work_data.effect = effect;
- INIT_WORK_ONSTACK(&work_data.work, cs40l50_add_worker);
- /* Push to the workqueue to serialize with playbacks */
- queue_work(vib->vib_wq, &work_data.work);
- flush_work(&work_data.work);
- destroy_work_on_stack(&work_data.work);
- kfree(work_data.custom_data);
- return work_data.error;
- }
- static void cs40l50_start_worker(struct work_struct *work)
- {
- struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
- struct cs40l50_vibra *vib = work_data->vib;
- struct cs40l50_effect *start_effect;
- if (pm_runtime_resume_and_get(vib->dev) < 0)
- goto err_free;
- start_effect = cs40l50_find_effect(work_data->effect->id, &vib->effect_head);
- if (start_effect) {
- while (--work_data->count >= 0) {
- vib->dsp.write(vib->dev, vib->regmap, start_effect->index);
- usleep_range(work_data->effect->replay.length,
- work_data->effect->replay.length + 100);
- }
- } else {
- dev_err(vib->dev, "Effect to play not found\n");
- }
- pm_runtime_mark_last_busy(vib->dev);
- pm_runtime_put_autosuspend(vib->dev);
- err_free:
- kfree(work_data);
- }
- static void cs40l50_stop_worker(struct work_struct *work)
- {
- struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
- struct cs40l50_vibra *vib = work_data->vib;
- if (pm_runtime_resume_and_get(vib->dev) < 0)
- return;
- vib->dsp.write(vib->dev, vib->regmap, vib->dsp.stop_cmd);
- pm_runtime_mark_last_busy(vib->dev);
- pm_runtime_put_autosuspend(vib->dev);
- kfree(work_data);
- }
- static int cs40l50_playback(struct input_dev *dev, int effect_id, int val)
- {
- struct cs40l50_vibra *vib = input_get_drvdata(dev);
- struct cs40l50_work *work_data;
- work_data = kzalloc(sizeof(*work_data), GFP_ATOMIC);
- if (!work_data)
- return -ENOMEM;
- work_data->vib = vib;
- if (val > 0) {
- work_data->effect = &dev->ff->effects[effect_id];
- work_data->count = val;
- INIT_WORK(&work_data->work, cs40l50_start_worker);
- } else {
- /* Stop the amplifier as device drives only one effect */
- INIT_WORK(&work_data->work, cs40l50_stop_worker);
- }
- queue_work(vib->vib_wq, &work_data->work);
- return 0;
- }
- static void cs40l50_erase_worker(struct work_struct *work)
- {
- struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
- struct cs40l50_effect *erase_effect, *owt_effect;
- struct cs40l50_vibra *vib = work_data->vib;
- int error;
- error = pm_runtime_resume_and_get(vib->dev);
- if (error)
- goto err_exit;
- erase_effect = cs40l50_find_effect(work_data->effect->id, &vib->effect_head);
- if (!erase_effect) {
- dev_err(vib->dev, "Effect to erase not found\n");
- error = -EINVAL;
- goto err_pm;
- }
- if (erase_effect->gpio_reg != CS40L50_GPIO_MAPPING_NONE) {
- error = regmap_write(vib->regmap, erase_effect->gpio_reg,
- CS40L50_GPIO_DISABLE);
- if (error)
- goto err_pm;
- }
- if (erase_effect->type == CS40L50_WVFRM_BANK_OWT) {
- error = vib->dsp.write(vib->dev, vib->regmap,
- vib->dsp.delete_owt_cmd |
- (erase_effect->index & 0xFF));
- if (error)
- goto err_pm;
- list_for_each_entry(owt_effect, &vib->effect_head, list)
- if (owt_effect->type == CS40L50_WVFRM_BANK_OWT &&
- owt_effect->index > erase_effect->index)
- owt_effect->index--;
- }
- list_del(&erase_effect->list);
- kfree(erase_effect);
- err_pm:
- pm_runtime_mark_last_busy(vib->dev);
- pm_runtime_put_autosuspend(vib->dev);
- err_exit:
- work_data->error = error;
- }
- static int cs40l50_erase(struct input_dev *dev, int effect_id)
- {
- struct cs40l50_vibra *vib = input_get_drvdata(dev);
- struct cs40l50_work work_data;
- work_data.vib = vib;
- work_data.effect = &dev->ff->effects[effect_id];
- INIT_WORK_ONSTACK(&work_data.work, cs40l50_erase_worker);
- /* Push to workqueue to serialize with playbacks */
- queue_work(vib->vib_wq, &work_data.work);
- flush_work(&work_data.work);
- destroy_work_on_stack(&work_data.work);
- return work_data.error;
- }
- static void cs40l50_remove_wq(void *data)
- {
- flush_workqueue(data);
- destroy_workqueue(data);
- }
- static int cs40l50_vibra_probe(struct platform_device *pdev)
- {
- struct cs40l50 *cs40l50 = dev_get_drvdata(pdev->dev.parent);
- struct cs40l50_vibra *vib;
- int error;
- vib = devm_kzalloc(pdev->dev.parent, sizeof(*vib), GFP_KERNEL);
- if (!vib)
- return -ENOMEM;
- vib->dev = cs40l50->dev;
- vib->regmap = cs40l50->regmap;
- vib->dsp = cs40l50_dsp;
- vib->input = devm_input_allocate_device(vib->dev);
- if (!vib->input)
- return -ENOMEM;
- vib->input->id.product = cs40l50->devid;
- vib->input->id.version = cs40l50->revid;
- vib->input->name = "cs40l50_vibra";
- input_set_drvdata(vib->input, vib);
- input_set_capability(vib->input, EV_FF, FF_PERIODIC);
- input_set_capability(vib->input, EV_FF, FF_CUSTOM);
- error = input_ff_create(vib->input, CS40L50_EFFECTS_MAX);
- if (error) {
- dev_err(vib->dev, "Failed to create input device\n");
- return error;
- }
- vib->input->ff->upload = cs40l50_add;
- vib->input->ff->playback = cs40l50_playback;
- vib->input->ff->erase = cs40l50_erase;
- INIT_LIST_HEAD(&vib->effect_head);
- vib->vib_wq = alloc_ordered_workqueue("vib_wq", WQ_HIGHPRI);
- if (!vib->vib_wq)
- return -ENOMEM;
- error = devm_add_action_or_reset(vib->dev, cs40l50_remove_wq, vib->vib_wq);
- if (error)
- return error;
- error = input_register_device(vib->input);
- if (error)
- return error;
- return 0;
- }
- static const struct platform_device_id cs40l50_vibra_id_match[] = {
- { "cs40l50-vibra", },
- {}
- };
- MODULE_DEVICE_TABLE(platform, cs40l50_vibra_id_match);
- static struct platform_driver cs40l50_vibra_driver = {
- .probe = cs40l50_vibra_probe,
- .id_table = cs40l50_vibra_id_match,
- .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");
|