summaryrefslogtreecommitdiff
path: root/sound/soc/sdca/sdca_interrupts.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/sdca/sdca_interrupts.c')
-rw-r--r--sound/soc/sdca/sdca_interrupts.c280
1 files changed, 224 insertions, 56 deletions
diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c
index 79bf3042f57d..8f6a2adfb6fb 100644
--- a/sound/soc/sdca/sdca_interrupts.c
+++ b/sound/soc/sdca/sdca_interrupts.c
@@ -11,13 +11,18 @@
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/device.h>
+#include <linux/dev_printk.h>
#include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_registers.h>
#include <sound/sdca.h>
+#include <sound/sdca_fdl.h>
#include <sound/sdca_function.h>
+#include <sound/sdca_hid.h>
#include <sound/sdca_interrupts.h>
+#include <sound/sdca_ump.h>
#include <sound/soc-component.h>
#include <sound/soc.h>
@@ -75,7 +80,7 @@ static const struct regmap_irq_chip sdca_irq_chip = {
static irqreturn_t base_handler(int irq, void *data)
{
struct sdca_interrupt *interrupt = data;
- struct device *dev = interrupt->component->dev;
+ struct device *dev = interrupt->dev;
dev_info(dev, "%s irq without full handling\n", interrupt->name);
@@ -85,19 +90,26 @@ static irqreturn_t base_handler(int irq, void *data)
static irqreturn_t function_status_handler(int irq, void *data)
{
struct sdca_interrupt *interrupt = data;
- struct device *dev = interrupt->component->dev;
+ struct device *dev = interrupt->dev;
+ irqreturn_t irqret = IRQ_NONE;
unsigned int reg, val;
unsigned long status;
unsigned int mask;
int ret;
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to resume for function status: %d\n", ret);
+ goto error;
+ }
+
reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id,
interrupt->control->sel, 0);
- ret = regmap_read(interrupt->component->regmap, reg, &val);
+ ret = regmap_read(interrupt->function_regmap, reg, &val);
if (ret < 0) {
dev_err(dev, "failed to read function status: %d\n", ret);
- return IRQ_NONE;
+ goto error;
}
dev_dbg(dev, "function status: %#x\n", val);
@@ -127,40 +139,50 @@ static irqreturn_t function_status_handler(int irq, void *data)
}
}
- ret = regmap_write(interrupt->component->regmap, reg, val);
+ ret = regmap_write(interrupt->function_regmap, reg, val);
if (ret < 0) {
dev_err(dev, "failed to clear function status: %d\n", ret);
- return IRQ_NONE;
+ goto error;
}
- return IRQ_HANDLED;
+ irqret = IRQ_HANDLED;
+error:
+ pm_runtime_put(dev);
+ return irqret;
}
static irqreturn_t detected_mode_handler(int irq, void *data)
{
struct sdca_interrupt *interrupt = data;
+ struct device *dev = interrupt->dev;
struct snd_soc_component *component = interrupt->component;
- struct device *dev = component->dev;
struct snd_soc_card *card = component->card;
struct rw_semaphore *rwsem = &card->snd_card->controls_rwsem;
struct snd_kcontrol *kctl = interrupt->priv;
struct snd_ctl_elem_value *ucontrol __free(kfree) = NULL;
struct soc_enum *soc_enum;
+ irqreturn_t irqret = IRQ_NONE;
unsigned int reg, val;
int ret;
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to resume for detected mode: %d\n", ret);
+ goto error;
+ }
+
if (!kctl) {
const char *name __free(kfree) = kasprintf(GFP_KERNEL, "%s %s",
interrupt->entity->label,
SDCA_CTL_SELECTED_MODE_NAME);
if (!name)
- return IRQ_NONE;
+ goto error;
kctl = snd_soc_component_get_kcontrol(component, name);
if (!kctl) {
dev_dbg(dev, "control not found: %s\n", name);
- return IRQ_NONE;
+ goto error;
}
interrupt->priv = kctl;
@@ -171,10 +193,10 @@ static irqreturn_t detected_mode_handler(int irq, void *data)
reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id,
interrupt->control->sel, 0);
- ret = regmap_read(component->regmap, reg, &val);
+ ret = regmap_read(interrupt->function_regmap, reg, &val);
if (ret < 0) {
dev_err(dev, "failed to read detected mode: %d\n", ret);
- return IRQ_NONE;
+ goto error;
}
switch (val) {
@@ -190,12 +212,12 @@ static irqreturn_t detected_mode_handler(int irq, void *data)
* detected mode is unknown we need to see what the device
* selected as a "safe" option.
*/
- regcache_drop_region(component->regmap, reg, reg);
+ regcache_drop_region(interrupt->function_regmap, reg, reg);
- ret = regmap_read(component->regmap, reg, &val);
+ ret = regmap_read(interrupt->function_regmap, reg, &val);
if (ret) {
dev_err(dev, "failed to re-check selected mode: %d\n", ret);
- return IRQ_NONE;
+ goto error;
}
break;
default:
@@ -206,7 +228,7 @@ static irqreturn_t detected_mode_handler(int irq, void *data)
ucontrol = kzalloc(sizeof(*ucontrol), GFP_KERNEL);
if (!ucontrol)
- return IRQ_NONE;
+ goto error;
ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(soc_enum, val);
@@ -215,12 +237,61 @@ static irqreturn_t detected_mode_handler(int irq, void *data)
up_write(rwsem);
if (ret < 0) {
dev_err(dev, "failed to update selected mode: %d\n", ret);
- return IRQ_NONE;
+ goto error;
}
snd_ctl_notify(card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
- return IRQ_HANDLED;
+ irqret = IRQ_HANDLED;
+error:
+ pm_runtime_put(dev);
+ return irqret;
+}
+
+static irqreturn_t hid_handler(int irq, void *data)
+{
+ struct sdca_interrupt *interrupt = data;
+ struct device *dev = interrupt->dev;
+ irqreturn_t irqret = IRQ_NONE;
+ int ret;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to resume for hid: %d\n", ret);
+ goto error;
+ }
+
+ ret = sdca_hid_process_report(interrupt);
+ if (ret)
+ goto error;
+
+ irqret = IRQ_HANDLED;
+error:
+ pm_runtime_put(dev);
+ return irqret;
+}
+
+static irqreturn_t fdl_owner_handler(int irq, void *data)
+{
+ struct sdca_interrupt *interrupt = data;
+ struct device *dev = interrupt->dev;
+ irqreturn_t irqret = IRQ_NONE;
+ int ret;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to resume for fdl: %d\n", ret);
+ goto error;
+ }
+
+ ret = sdca_fdl_process(interrupt);
+ if (ret)
+ goto error;
+
+ irqret = IRQ_HANDLED;
+error:
+ pm_runtime_put(dev);
+ return irqret;
}
static int sdca_irq_request_locked(struct device *dev,
@@ -240,6 +311,8 @@ static int sdca_irq_request_locked(struct device *dev,
if (ret)
return ret;
+ info->irqs[sdca_irq].irq = irq;
+
dev_dbg(dev, "requested irq %d for %s\n", irq, name);
return 0;
@@ -279,14 +352,14 @@ int sdca_irq_request(struct device *dev, struct sdca_interrupt_info *info,
return ret;
}
- info->irqs[sdca_irq].externally_requested = true;
-
return 0;
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_request, "SND_SOC_SDCA");
/**
* sdca_irq_data_populate - Populate common interrupt data
+ * @dev: Pointer to the Function device.
+ * @regmap: Pointer to the Function regmap.
* @component: Pointer to the ASoC component for the Function.
* @function: Pointer to the SDCA Function.
* @entity: Pointer to the SDCA Entity.
@@ -295,21 +368,31 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_request, "SND_SOC_SDCA");
*
* Return: Zero on success, and a negative error code on failure.
*/
-int sdca_irq_data_populate(struct snd_soc_component *component,
+int sdca_irq_data_populate(struct device *dev, struct regmap *regmap,
+ struct snd_soc_component *component,
struct sdca_function_data *function,
struct sdca_entity *entity,
struct sdca_control *control,
struct sdca_interrupt *interrupt)
{
- struct device *dev = component->dev;
const char *name;
+ if (!dev && component)
+ dev = component->dev;
+ if (!dev)
+ return -ENODEV;
+
name = devm_kasprintf(dev, GFP_KERNEL, "%s %s %s", function->desc->name,
entity->label, control->label);
if (!name)
return -ENOMEM;
interrupt->name = name;
+ interrupt->dev = dev;
+ if (!regmap && component)
+ interrupt->function_regmap = component->regmap;
+ else
+ interrupt->function_regmap = regmap;
interrupt->component = component;
interrupt->function = function;
interrupt->entity = entity;
@@ -319,6 +402,92 @@ int sdca_irq_data_populate(struct snd_soc_component *component,
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_data_populate, "SND_SOC_SDCA");
+static struct sdca_interrupt *get_interrupt_data(struct device *dev, int irq,
+ struct sdca_interrupt_info *info)
+{
+ if (irq == SDCA_NO_INTERRUPT) {
+ return NULL;
+ } else if (irq < 0 || irq >= SDCA_MAX_INTERRUPTS) {
+ dev_err(dev, "bad irq position: %d\n", irq);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (info->irqs[irq].irq) {
+ dev_dbg(dev, "skipping irq %d, already requested\n", irq);
+ return NULL;
+ }
+
+ return &info->irqs[irq];
+}
+
+/**
+ * sdca_irq_populate_early - process pre-audio card IRQ registrations
+ * @dev: Device pointer for SDCA Function.
+ * @regmap: Regmap pointer for the SDCA Function.
+ * @function: Pointer to the SDCA Function.
+ * @info: Pointer to the SDCA interrupt info for this device.
+ *
+ * This is intended to be used as part of the Function boot process. It
+ * can be called before the soundcard is registered (ie. doesn't depend
+ * on component) and will register the FDL interrupts.
+ *
+ * Return: Zero on success, and a negative error code on failure.
+ */
+int sdca_irq_populate_early(struct device *dev, struct regmap *regmap,
+ struct sdca_function_data *function,
+ struct sdca_interrupt_info *info)
+{
+ int i, j;
+
+ guard(mutex)(&info->irq_lock);
+
+ for (i = 0; i < function->num_entities; i++) {
+ struct sdca_entity *entity = &function->entities[i];
+
+ for (j = 0; j < entity->num_controls; j++) {
+ struct sdca_control *control = &entity->controls[j];
+ int irq = control->interrupt_position;
+ struct sdca_interrupt *interrupt;
+ int ret;
+
+ interrupt = get_interrupt_data(dev, irq, info);
+ if (IS_ERR(interrupt))
+ return PTR_ERR(interrupt);
+ else if (!interrupt)
+ continue;
+
+ switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
+ case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER):
+ ret = sdca_irq_data_populate(dev, regmap, NULL,
+ function, entity,
+ control, interrupt);
+ if (ret)
+ return ret;
+
+ ret = sdca_fdl_alloc_state(interrupt);
+ if (ret)
+ return ret;
+
+ ret = sdca_irq_request_locked(dev, info, irq,
+ interrupt->name,
+ fdl_owner_handler,
+ interrupt);
+ if (ret) {
+ dev_err(dev, "failed to request irq %s: %d\n",
+ interrupt->name, ret);
+ return ret;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(sdca_irq_populate_early, "SND_SOC_SDCA");
+
/**
* sdca_irq_populate - Request all the individual IRQs for an SDCA Function
* @function: Pointer to the SDCA Function.
@@ -348,37 +517,36 @@ int sdca_irq_populate(struct sdca_function_data *function,
irq_handler_t handler;
int ret;
- if (irq == SDCA_NO_INTERRUPT) {
+ interrupt = get_interrupt_data(dev, irq, info);
+ if (IS_ERR(interrupt))
+ return PTR_ERR(interrupt);
+ else if (!interrupt)
continue;
- } else if (irq < 0 || irq >= SDCA_MAX_INTERRUPTS) {
- dev_err(dev, "bad irq position: %d\n", irq);
- return -EINVAL;
- }
- interrupt = &info->irqs[irq];
-
- if (interrupt->externally_requested) {
- dev_dbg(dev,
- "skipping irq %d, externally requested\n",
- irq);
- continue;
- }
-
- ret = sdca_irq_data_populate(component, function, entity,
- control, interrupt);
+ ret = sdca_irq_data_populate(dev, NULL, component,
+ function, entity, control,
+ interrupt);
if (ret)
return ret;
handler = base_handler;
- switch (entity->type) {
- case SDCA_ENTITY_TYPE_ENTITY_0:
- if (control->sel == SDCA_CTL_ENTITY_0_FUNCTION_STATUS)
- handler = function_status_handler;
+ switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
+ case SDCA_CTL_TYPE_S(ENTITY_0, FUNCTION_STATUS):
+ handler = function_status_handler;
+ break;
+ case SDCA_CTL_TYPE_S(GE, DETECTED_MODE):
+ handler = detected_mode_handler;
+ break;
+ case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER):
+ ret = sdca_fdl_alloc_state(interrupt);
+ if (ret)
+ return ret;
+
+ handler = fdl_owner_handler;
break;
- case SDCA_ENTITY_TYPE_GE:
- if (control->sel == SDCA_CTL_GE_DETECTED_MODE)
- handler = detected_mode_handler;
+ case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER):
+ handler = hid_handler;
break;
default:
break;
@@ -400,7 +568,7 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_populate, "SND_SOC_SDCA");
/**
* sdca_irq_allocate - allocate an SDCA interrupt structure for a device
- * @dev: Device pointer against which things should be allocated.
+ * @sdev: Device pointer against which things should be allocated.
* @regmap: regmap to be used for accessing the SDCA IRQ registers.
* @irq: The interrupt number.
*
@@ -411,34 +579,34 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_populate, "SND_SOC_SDCA");
* Return: A pointer to the allocated sdca_interrupt_info struct, or an
* error code.
*/
-struct sdca_interrupt_info *sdca_irq_allocate(struct device *dev,
+struct sdca_interrupt_info *sdca_irq_allocate(struct device *sdev,
struct regmap *regmap, int irq)
{
struct sdca_interrupt_info *info;
- int ret;
+ int ret, i;
- info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ info = devm_kzalloc(sdev, sizeof(*info), GFP_KERNEL);
if (!info)
return ERR_PTR(-ENOMEM);
info->irq_chip = sdca_irq_chip;
- ret = devm_mutex_init(dev, &info->irq_lock);
+ for (i = 0; i < ARRAY_SIZE(info->irqs); i++)
+ info->irqs[i].device_regmap = regmap;
+
+ ret = devm_mutex_init(sdev, &info->irq_lock);
if (ret)
return ERR_PTR(ret);
- ret = devm_regmap_add_irq_chip(dev, regmap, irq, IRQF_ONESHOT, 0,
+ ret = devm_regmap_add_irq_chip(sdev, regmap, irq, IRQF_ONESHOT, 0,
&info->irq_chip, &info->irq_data);
if (ret) {
- dev_err(dev, "failed to register irq chip: %d\n", ret);
+ dev_err(sdev, "failed to register irq chip: %d\n", ret);
return ERR_PTR(ret);
}
- dev_dbg(dev, "registered on irq %d\n", irq);
+ dev_dbg(sdev, "registered on irq %d\n", irq);
return info;
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_allocate, "SND_SOC_SDCA");
-
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("SDCA IRQ library");