Skip to content

Commit

Permalink
Merge branch 'ib-mfd-firmware-input-sound-soc-6.11' into ibs-for-mfd-…
Browse files Browse the repository at this point in the history
…merged
  • Loading branch information
lag-linaro committed Jul 4, 2024
2 parents ecad8fb + c486def commit 2d21e97
Show file tree
Hide file tree
Showing 16 changed files with 2,148 additions and 0 deletions.
68 changes: 68 additions & 0 deletions Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/input/cirrus,cs40l50.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: Cirrus Logic CS40L50 Advanced Haptic Driver

maintainers:
- James Ogletree <jogletre@opensource.cirrus.com>

description:
CS40L50 is a haptic driver with waveform memory,
integrated DSP, and closed-loop algorithms.

properties:
compatible:
enum:
- cirrus,cs40l50

reg:
maxItems: 1

interrupts:
maxItems: 1

reset-gpios:
maxItems: 1

vdd-a-supply:
description: Power supply for internal analog circuits.

vdd-p-supply:
description: Power supply for always-on circuits.

vdd-io-supply:
description: Power supply for digital input/output.

vdd-b-supply:
description: Power supply for the boost converter.

required:
- compatible
- reg
- interrupts
- reset-gpios
- vdd-io-supply

additionalProperties: false

examples:
- |
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
haptic-driver@34 {
compatible = "cirrus,cs40l50";
reg = <0x34>;
interrupt-parent = <&gpio>;
interrupts = <113 IRQ_TYPE_LEVEL_LOW>;
reset-gpios = <&gpio 112 GPIO_ACTIVE_LOW>;
vdd-io-supply = <&vreg>;
};
};
12 changes: 12 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -5211,6 +5211,18 @@ F: sound/pci/hda/hda_component*
F: sound/pci/hda/hda_cs_dsp_ctl.*
F: sound/soc/codecs/cs*

CIRRUS LOGIC HAPTIC DRIVERS
M: James Ogletree <jogletre@opensource.cirrus.com>
M: Fred Treven <fred.treven@cirrus.com>
M: Ben Bright <ben.bright@cirrus.com>
L: patches@opensource.cirrus.com
S: Supported
F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
F: drivers/input/misc/cs40l*
F: drivers/mfd/cs40l*
F: include/linux/mfd/cs40l*
F: sound/soc/codecs/cs40l*

CIRRUS LOGIC DSP FIRMWARE DRIVER
M: Simon Trimmer <simont@opensource.cirrus.com>
M: Charles Keepax <ckeepax@opensource.cirrus.com>
Expand Down
278 changes: 278 additions & 0 deletions drivers/firmware/cirrus/cs_dsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@
#define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff
#define HALO_MPU_VIO_ERR_SRC_SHIFT 0

/*
* Write Sequence
*/
#define WSEQ_OP_MAX_WORDS 3
#define WSEQ_END_OF_SCRIPT 0xFFFFFF

struct cs_dsp_ops {
bool (*validate_version)(struct cs_dsp *dsp, unsigned int version);
unsigned int (*parse_sizes)(struct cs_dsp *dsp,
Expand Down Expand Up @@ -3398,6 +3404,278 @@ int cs_dsp_chunk_read(struct cs_dsp_chunk *ch, int nbits)
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_read, FW_CS_DSP);


struct cs_dsp_wseq_op {
struct list_head list;
u32 address;
u32 data;
u16 offset;
u8 operation;
};

static void cs_dsp_wseq_clear(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq)
{
struct cs_dsp_wseq_op *op, *op_tmp;

list_for_each_entry_safe(op, op_tmp, &wseq->ops, list) {
list_del(&op->list);
devm_kfree(dsp->dev, op);
}
}

static int cs_dsp_populate_wseq(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq)
{
struct cs_dsp_wseq_op *op = NULL;
struct cs_dsp_chunk chunk;
u8 *words;
int ret;

if (!wseq->ctl) {
cs_dsp_err(dsp, "No control for write sequence\n");
return -EINVAL;
}

words = kzalloc(wseq->ctl->len, GFP_KERNEL);
if (!words)
return -ENOMEM;

ret = cs_dsp_coeff_read_ctrl(wseq->ctl, 0, words, wseq->ctl->len);
if (ret) {
cs_dsp_err(dsp, "Failed to read %s: %d\n", wseq->ctl->subname, ret);
goto err_free;
}

INIT_LIST_HEAD(&wseq->ops);

chunk = cs_dsp_chunk(words, wseq->ctl->len);

while (!cs_dsp_chunk_end(&chunk)) {
op = devm_kzalloc(dsp->dev, sizeof(*op), GFP_KERNEL);
if (!op) {
ret = -ENOMEM;
goto err_free;
}

op->offset = cs_dsp_chunk_bytes(&chunk);
op->operation = cs_dsp_chunk_read(&chunk, 8);

switch (op->operation) {
case CS_DSP_WSEQ_END:
op->data = WSEQ_END_OF_SCRIPT;
break;
case CS_DSP_WSEQ_UNLOCK:
op->data = cs_dsp_chunk_read(&chunk, 16);
break;
case CS_DSP_WSEQ_ADDR8:
op->address = cs_dsp_chunk_read(&chunk, 8);
op->data = cs_dsp_chunk_read(&chunk, 32);
break;
case CS_DSP_WSEQ_H16:
case CS_DSP_WSEQ_L16:
op->address = cs_dsp_chunk_read(&chunk, 24);
op->data = cs_dsp_chunk_read(&chunk, 16);
break;
case CS_DSP_WSEQ_FULL:
op->address = cs_dsp_chunk_read(&chunk, 32);
op->data = cs_dsp_chunk_read(&chunk, 32);
break;
default:
ret = -EINVAL;
cs_dsp_err(dsp, "Unsupported op: %X\n", op->operation);
devm_kfree(dsp->dev, op);
goto err_free;
}

list_add_tail(&op->list, &wseq->ops);

if (op->operation == CS_DSP_WSEQ_END)
break;
}

if (op && op->operation != CS_DSP_WSEQ_END) {
cs_dsp_err(dsp, "%s missing end terminator\n", wseq->ctl->subname);
ret = -ENOENT;
}

err_free:
kfree(words);

return ret;
}

/**
* cs_dsp_wseq_init() - Initialize write sequences contained within the loaded DSP firmware
* @dsp: Pointer to DSP structure
* @wseqs: List of write sequences to initialize
* @num_wseqs: Number of write sequences to initialize
*
* Return: Zero for success, a negative number on error.
*/
int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs)
{
int i, ret;

lockdep_assert_held(&dsp->pwr_lock);

for (i = 0; i < num_wseqs; i++) {
ret = cs_dsp_populate_wseq(dsp, &wseqs[i]);
if (ret) {
cs_dsp_wseq_clear(dsp, &wseqs[i]);
return ret;
}
}

return 0;
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_init, FW_CS_DSP);

static struct cs_dsp_wseq_op *cs_dsp_wseq_find_op(u32 addr, u8 op_code,
struct list_head *wseq_ops)
{
struct cs_dsp_wseq_op *op;

list_for_each_entry(op, wseq_ops, list) {
if (op->operation == op_code && op->address == addr)
return op;
}

return NULL;
}

/**
* cs_dsp_wseq_write() - Add or update an entry in a write sequence
* @dsp: Pointer to a DSP structure
* @wseq: Write sequence to write to
* @addr: Address of the register to be written to
* @data: Data to be written
* @op_code: The type of operation of the new entry
* @update: If true, searches for the first entry in the write sequence with
* the same address and op_code, and replaces it. If false, creates a new entry
* at the tail
*
* This function formats register address and value pairs into the format
* required for write sequence entries, and either updates or adds the
* new entry into the write sequence.
*
* If update is set to true and no matching entry is found, it will add a new entry.
*
* Return: Zero for success, a negative number on error.
*/
int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
u32 addr, u32 data, u8 op_code, bool update)
{
struct cs_dsp_wseq_op *op_end, *op_new = NULL;
u32 words[WSEQ_OP_MAX_WORDS];
struct cs_dsp_chunk chunk;
int new_op_size, ret;

if (update)
op_new = cs_dsp_wseq_find_op(addr, op_code, &wseq->ops);

/* If entry to update is not found, treat it as a new operation */
if (!op_new) {
op_end = cs_dsp_wseq_find_op(0, CS_DSP_WSEQ_END, &wseq->ops);
if (!op_end) {
cs_dsp_err(dsp, "Missing terminator for %s\n", wseq->ctl->subname);
return -EINVAL;
}

op_new = devm_kzalloc(dsp->dev, sizeof(*op_new), GFP_KERNEL);
if (!op_new)
return -ENOMEM;

op_new->operation = op_code;
op_new->address = addr;
op_new->offset = op_end->offset;
update = false;
}

op_new->data = data;

chunk = cs_dsp_chunk(words, sizeof(words));
cs_dsp_chunk_write(&chunk, 8, op_new->operation);

switch (op_code) {
case CS_DSP_WSEQ_FULL:
cs_dsp_chunk_write(&chunk, 32, op_new->address);
cs_dsp_chunk_write(&chunk, 32, op_new->data);
break;
case CS_DSP_WSEQ_L16:
case CS_DSP_WSEQ_H16:
cs_dsp_chunk_write(&chunk, 24, op_new->address);
cs_dsp_chunk_write(&chunk, 16, op_new->data);
break;
default:
ret = -EINVAL;
cs_dsp_err(dsp, "Operation %X not supported\n", op_code);
goto op_new_free;
}

new_op_size = cs_dsp_chunk_bytes(&chunk);

if (!update) {
if (wseq->ctl->len - op_end->offset < new_op_size) {
cs_dsp_err(dsp, "Not enough memory in %s for entry\n", wseq->ctl->subname);
ret = -E2BIG;
goto op_new_free;
}

op_end->offset += new_op_size;

ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_end->offset / sizeof(u32),
&op_end->data, sizeof(u32));
if (ret)
goto op_new_free;

list_add_tail(&op_new->list, &op_end->list);
}

ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_new->offset / sizeof(u32),
words, new_op_size);
if (ret)
goto op_new_free;

return 0;

op_new_free:
devm_kfree(dsp->dev, op_new);

return ret;
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_write, FW_CS_DSP);

/**
* cs_dsp_wseq_multi_write() - Add or update multiple entries in a write sequence
* @dsp: Pointer to a DSP structure
* @wseq: Write sequence to write to
* @reg_seq: List of address-data pairs
* @num_regs: Number of address-data pairs
* @op_code: The types of operations of the new entries
* @update: If true, searches for the first entry in the write sequence with
* the same address and op_code, and replaces it. If false, creates a new entry
* at the tail
*
* This function calls cs_dsp_wseq_write() for multiple address-data pairs.
*
* Return: Zero for success, a negative number on error.
*/
int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
const struct reg_sequence *reg_seq, int num_regs,
u8 op_code, bool update)
{
int i, ret;

for (i = 0; i < num_regs; i++) {
ret = cs_dsp_wseq_write(dsp, wseq, reg_seq[i].reg,
reg_seq[i].def, op_code, update);
if (ret)
return ret;
}

return 0;
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_multi_write, FW_CS_DSP);

MODULE_DESCRIPTION("Cirrus Logic DSP Support");
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
MODULE_LICENSE("GPL v2");
10 changes: 10 additions & 0 deletions drivers/input/misc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,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
Expand Down
Loading

0 comments on commit 2d21e97

Please sign in to comment.