ALSA: hda: Adding support for CS35L56 on HDA

Merge series from Richard Fitzgerald <rf@opensource.cirrus.com>:

This set of patches adds support for using the CS35L56 boosted smart
amplifier on HDA systems. In these systems the CS35L56 audio is
routed through a HDA-to-I2S bridge codec.

This doesn't include the changes to the Realtek driver to actually hook
up the CS35L56 driver, because we don't yet have the QUIRK IDs to
associate it with. But we want to publish the driver now so that it is
available for bringing up hardware with the CS35L56.

The first 9 patches are moving code out of the ASoC driver and into the
shared library so that it can be shared with the HDA driver.

Patch #10 fixes missing #includes in the HDA headers so that the CS35L56
driver doesn't have to #include headers that it doesn't use.
This commit is contained in:
Mark Brown 2023-07-24 18:42:26 +01:00
commit 85d12eda23
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
15 changed files with 1877 additions and 591 deletions

View File

@ -252,15 +252,40 @@
#define CS35L56_NUM_BULK_SUPPLIES 3 #define CS35L56_NUM_BULK_SUPPLIES 3
#define CS35L56_NUM_DSP_REGIONS 5 #define CS35L56_NUM_DSP_REGIONS 5
struct cs35l56_base {
struct device *dev;
struct regmap *regmap;
int irq;
struct mutex irq_lock;
u8 rev;
bool init_done;
bool fw_patched;
bool secured;
bool can_hibernate;
struct gpio_desc *reset_gpio;
};
extern struct regmap_config cs35l56_regmap_i2c; extern struct regmap_config cs35l56_regmap_i2c;
extern struct regmap_config cs35l56_regmap_spi; extern struct regmap_config cs35l56_regmap_spi;
extern struct regmap_config cs35l56_regmap_sdw; extern struct regmap_config cs35l56_regmap_sdw;
extern const struct cs_dsp_region cs35l56_dsp1_regions[CS35L56_NUM_DSP_REGIONS];
extern const char * const cs35l56_tx_input_texts[CS35L56_NUM_INPUT_SRC]; extern const char * const cs35l56_tx_input_texts[CS35L56_NUM_INPUT_SRC];
extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC]; extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC];
int cs35l56_set_patch(struct regmap *regmap); int cs35l56_set_patch(struct cs35l56_base *cs35l56_base);
int cs35l56_mbox_send(struct cs35l56_base *cs35l56_base, unsigned int command);
int cs35l56_firmware_shutdown(struct cs35l56_base *cs35l56_base);
int cs35l56_wait_for_firmware_boot(struct cs35l56_base *cs35l56_base);
void cs35l56_wait_control_port_ready(void);
void cs35l56_wait_min_reset_pulse(void);
void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire);
int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq);
irqreturn_t cs35l56_irq(int irq, void *data);
int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base);
int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base);
int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire);
void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp);
int cs35l56_hw_init(struct cs35l56_base *cs35l56_base);
int cs35l56_get_bclk_freq_id(unsigned int freq); int cs35l56_get_bclk_freq_id(unsigned int freq);
void cs35l56_fill_supply_names(struct regulator_bulk_data *data); void cs35l56_fill_supply_names(struct regulator_bulk_data *data);

View File

@ -130,6 +130,37 @@ config SND_HDA_SCODEC_CS35L41_SPI
comment "Set to Y if you want auto-loading the side codec driver" comment "Set to Y if you want auto-loading the side codec driver"
depends on SND_HDA=y && SND_HDA_SCODEC_CS35L41_SPI=m depends on SND_HDA=y && SND_HDA_SCODEC_CS35L41_SPI=m
config SND_HDA_SCODEC_CS35L56
tristate
config SND_HDA_SCODEC_CS35L56_I2C
tristate "Build CS35L56 HD-audio side codec support for I2C Bus"
depends on I2C
depends on ACPI || COMPILE_TEST
depends on SND_SOC
select CS_DSP
select SND_HDA_GENERIC
select SND_SOC_CS35L56_SHARED
select SND_HDA_SCODEC_CS35L56
select SND_HDA_CS_DSP_CONTROLS
help
Say Y or M here to include CS35L56 amplifier support with
I2C control.
config SND_HDA_SCODEC_CS35L56_SPI
tristate "Build CS35L56 HD-audio side codec support for SPI Bus"
depends on SPI_MASTER
depends on ACPI || COMPILE_TEST
depends on SND_SOC
select CS_DSP
select SND_HDA_GENERIC
select SND_SOC_CS35L56_SHARED
select SND_HDA_SCODEC_CS35L56
select SND_HDA_CS_DSP_CONTROLS
help
Say Y or M here to include CS35L56 amplifier support with
SPI control.
config SND_HDA_CODEC_REALTEK config SND_HDA_CODEC_REALTEK
tristate "Build Realtek HD-audio codec support" tristate "Build Realtek HD-audio codec support"
select SND_HDA_GENERIC select SND_HDA_GENERIC

View File

@ -31,6 +31,9 @@ snd-hda-codec-hdmi-objs := patch_hdmi.o hda_eld.o
snd-hda-scodec-cs35l41-objs := cs35l41_hda.o snd-hda-scodec-cs35l41-objs := cs35l41_hda.o
snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o
snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o
snd-hda-scodec-cs35l56-objs := cs35l56_hda.o
snd-hda-scodec-cs35l56-i2c-objs := cs35l56_hda_i2c.o
snd-hda-scodec-cs35l56-spi-objs := cs35l56_hda_spi.o
snd-hda-cs-dsp-ctls-objs := hda_cs_dsp_ctl.o snd-hda-cs-dsp-ctls-objs := hda_cs_dsp_ctl.o
# common driver # common driver
@ -55,6 +58,9 @@ obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o
obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o
obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o
obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o
obj-$(CONFIG_SND_HDA_SCODEC_CS35L56) += snd-hda-scodec-cs35l56.o
obj-$(CONFIG_SND_HDA_SCODEC_CS35L56_I2C) += snd-hda-scodec-cs35l56-i2c.o
obj-$(CONFIG_SND_HDA_SCODEC_CS35L56_SPI) += snd-hda-scodec-cs35l56-spi.o
obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o
# this must be the last entry after codec drivers; # this must be the last entry after codec drivers;

995
sound/pci/hda/cs35l56_hda.c Normal file
View File

@ -0,0 +1,995 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// HDA audio driver for Cirrus Logic CS35L56 smart amp
//
// Copyright (C) 2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
//
#include <linux/acpi.h>
#include <linux/debugfs.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/hda_codec.h>
#include <sound/tlv.h>
#include "cs35l56_hda.h"
#include "hda_component.h"
#include "hda_cs_dsp_ctl.h"
#include "hda_generic.h"
/*
* The cs35l56_hda_dai_config[] reg sequence configures the device as
* ASP1_BCLK_FREQ = 3.072 MHz
* ASP1_RX_WIDTH = 32 cycles per slot, ASP1_TX_WIDTH = 32 cycles per slot, ASP1_FMT = I2S
* ASP1_DOUT_HIZ_CONTROL = Hi-Z during unused timeslots
* ASP1_RX_WL = 24 bits per sample
* ASP1_TX_WL = 24 bits per sample
* ASP1_RXn_EN 1..3 and ASP1_TXn_EN 1..4 disabled
*/
static const struct reg_sequence cs35l56_hda_dai_config[] = {
{ CS35L56_ASP1_CONTROL1, 0x00000021 },
{ CS35L56_ASP1_CONTROL2, 0x20200200 },
{ CS35L56_ASP1_CONTROL3, 0x00000003 },
{ CS35L56_ASP1_DATA_CONTROL5, 0x00000018 },
{ CS35L56_ASP1_DATA_CONTROL1, 0x00000018 },
{ CS35L56_ASP1_ENABLES1, 0x00000000 },
};
static void cs35l56_hda_play(struct cs35l56_hda *cs35l56)
{
unsigned int val;
int ret;
pm_runtime_get_sync(cs35l56->base.dev);
ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PLAY);
if (ret == 0) {
/* Wait for firmware to enter PS0 power state */
ret = regmap_read_poll_timeout(cs35l56->base.regmap,
CS35L56_TRANSDUCER_ACTUAL_PS,
val, (val == CS35L56_PS0),
CS35L56_PS0_POLL_US,
CS35L56_PS0_TIMEOUT_US);
if (ret)
dev_warn(cs35l56->base.dev, "PS0 wait failed: %d\n", ret);
}
regmap_set_bits(cs35l56->base.regmap, CS35L56_ASP1_ENABLES1,
BIT(CS35L56_ASP_RX1_EN_SHIFT) | BIT(CS35L56_ASP_RX2_EN_SHIFT) |
cs35l56->asp_tx_mask);
cs35l56->playing = true;
}
static void cs35l56_hda_pause(struct cs35l56_hda *cs35l56)
{
cs35l56->playing = false;
cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PAUSE);
regmap_clear_bits(cs35l56->base.regmap, CS35L56_ASP1_ENABLES1,
BIT(CS35L56_ASP_RX1_EN_SHIFT) | BIT(CS35L56_ASP_RX2_EN_SHIFT) |
BIT(CS35L56_ASP_TX1_EN_SHIFT) | BIT(CS35L56_ASP_TX2_EN_SHIFT) |
BIT(CS35L56_ASP_TX3_EN_SHIFT) | BIT(CS35L56_ASP_TX4_EN_SHIFT));
pm_runtime_mark_last_busy(cs35l56->base.dev);
pm_runtime_put_autosuspend(cs35l56->base.dev);
}
static void cs35l56_hda_playback_hook(struct device *dev, int action)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
dev_dbg(cs35l56->base.dev, "%s()%d: action: %d\n", __func__, __LINE__, action);
switch (action) {
case HDA_GEN_PCM_ACT_PREPARE:
if (cs35l56->playing)
break;
/* If we're suspended: flag that resume should start playback */
if (cs35l56->suspended) {
cs35l56->playing = true;
break;
}
cs35l56_hda_play(cs35l56);
break;
case HDA_GEN_PCM_ACT_CLEANUP:
if (!cs35l56->playing)
break;
cs35l56_hda_pause(cs35l56);
break;
default:
break;
}
}
static int __maybe_unused cs35l56_hda_runtime_suspend(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
if (cs35l56->cs_dsp.booted)
cs_dsp_stop(&cs35l56->cs_dsp);
return cs35l56_runtime_suspend_common(&cs35l56->base);
}
static int __maybe_unused cs35l56_hda_runtime_resume(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
int ret;
ret = cs35l56_runtime_resume_common(&cs35l56->base, false);
if (ret < 0)
return ret;
if (cs35l56->cs_dsp.booted) {
ret = cs_dsp_run(&cs35l56->cs_dsp);
if (ret) {
dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret);
goto err;
}
}
return 0;
err:
cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE);
regmap_write(cs35l56->base.regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
CS35L56_MBOX_CMD_HIBERNATE_NOW);
regcache_cache_only(cs35l56->base.regmap, true);
return ret;
}
static int cs35l56_hda_mixer_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = CS35L56_NUM_INPUT_SRC;
if (uinfo->value.enumerated.item >= CS35L56_NUM_INPUT_SRC)
uinfo->value.enumerated.item = CS35L56_NUM_INPUT_SRC - 1;
strscpy(uinfo->value.enumerated.name, cs35l56_tx_input_texts[uinfo->value.enumerated.item],
sizeof(uinfo->value.enumerated.name));
return 0;
}
static int cs35l56_hda_mixer_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
unsigned int reg_val;
int i;
regmap_read(cs35l56->base.regmap, kcontrol->private_value, &reg_val);
reg_val &= CS35L56_ASP_TXn_SRC_MASK;
for (i = 0; i < CS35L56_NUM_INPUT_SRC; ++i) {
if (cs35l56_tx_input_values[i] == reg_val) {
ucontrol->value.enumerated.item[0] = i;
break;
}
}
return 0;
}
static int cs35l56_hda_mixer_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
unsigned int item = ucontrol->value.enumerated.item[0];
bool changed;
if (item >= CS35L56_NUM_INPUT_SRC)
return -EINVAL;
regmap_update_bits_check(cs35l56->base.regmap, kcontrol->private_value,
CS35L56_INPUT_MASK, cs35l56_tx_input_values[item],
&changed);
return changed;
}
static int cs35l56_hda_posture_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = CS35L56_MAIN_POSTURE_MIN;
uinfo->value.integer.max = CS35L56_MAIN_POSTURE_MAX;
return 0;
}
static int cs35l56_hda_posture_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
unsigned int pos;
int ret;
ret = regmap_read(cs35l56->base.regmap, CS35L56_MAIN_POSTURE_NUMBER, &pos);
if (ret)
return ret;
ucontrol->value.integer.value[0] = pos;
return ret;
}
static int cs35l56_hda_posture_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
unsigned long pos = ucontrol->value.integer.value[0];
bool changed;
int ret;
if ((pos < CS35L56_MAIN_POSTURE_MIN) ||
(pos > CS35L56_MAIN_POSTURE_MAX))
return -EINVAL;
ret = regmap_update_bits_check(cs35l56->base.regmap,
CS35L56_MAIN_POSTURE_NUMBER,
CS35L56_MAIN_POSTURE_MASK,
pos, &changed);
if (ret)
return ret;
return changed;
}
static const struct {
const char *name;
unsigned int reg;
} cs35l56_hda_mixer_controls[] = {
{ "ASP1 TX1 Source", CS35L56_ASP1TX1_INPUT },
{ "ASP1 TX2 Source", CS35L56_ASP1TX2_INPUT },
{ "ASP1 TX3 Source", CS35L56_ASP1TX3_INPUT },
{ "ASP1 TX4 Source", CS35L56_ASP1TX4_INPUT },
};
static const DECLARE_TLV_DB_SCALE(cs35l56_hda_vol_tlv, -10000, 25, 0);
static int cs35l56_hda_vol_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.step = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = CS35L56_MAIN_RENDER_USER_VOLUME_MAX -
CS35L56_MAIN_RENDER_USER_VOLUME_MIN;
return 0;
}
static int cs35l56_hda_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
unsigned int raw_vol;
int vol;
int ret;
ret = regmap_read(cs35l56->base.regmap, CS35L56_MAIN_RENDER_USER_VOLUME, &raw_vol);
if (ret)
return ret;
vol = (s16)(raw_vol & 0xFFFF);
vol >>= CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT;
if (vol & BIT(CS35L56_MAIN_RENDER_USER_VOLUME_SIGNBIT))
vol |= ~((int)(BIT(CS35L56_MAIN_RENDER_USER_VOLUME_SIGNBIT) - 1));
ucontrol->value.integer.value[0] = vol - CS35L56_MAIN_RENDER_USER_VOLUME_MIN;
return 0;
}
static int cs35l56_hda_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct cs35l56_hda *cs35l56 = (struct cs35l56_hda *)kcontrol->private_data;
long vol = ucontrol->value.integer.value[0];
unsigned int raw_vol;
bool changed;
int ret;
if ((vol < 0) || (vol > (CS35L56_MAIN_RENDER_USER_VOLUME_MAX -
CS35L56_MAIN_RENDER_USER_VOLUME_MIN)))
return -EINVAL;
raw_vol = (vol + CS35L56_MAIN_RENDER_USER_VOLUME_MIN) <<
CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT;
ret = regmap_update_bits_check(cs35l56->base.regmap,
CS35L56_MAIN_RENDER_USER_VOLUME,
CS35L56_MAIN_RENDER_USER_VOLUME_MASK,
raw_vol, &changed);
if (ret)
return ret;
return changed;
}
static void cs35l56_hda_create_controls(struct cs35l56_hda *cs35l56)
{
struct snd_kcontrol_new ctl_template = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = cs35l56_hda_posture_info,
.get = cs35l56_hda_posture_get,
.put = cs35l56_hda_posture_put,
};
char name[64];
int i;
snprintf(name, sizeof(name), "%s Posture Number", cs35l56->amp_name);
ctl_template.name = name;
cs35l56->posture_ctl = snd_ctl_new1(&ctl_template, cs35l56);
if (snd_ctl_add(cs35l56->codec->card, cs35l56->posture_ctl))
dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n", ctl_template.name);
/* Mixer controls */
ctl_template.info = cs35l56_hda_mixer_info;
ctl_template.get = cs35l56_hda_mixer_get;
ctl_template.put = cs35l56_hda_mixer_put;
BUILD_BUG_ON(ARRAY_SIZE(cs35l56->mixer_ctl) != ARRAY_SIZE(cs35l56_hda_mixer_controls));
for (i = 0; i < ARRAY_SIZE(cs35l56_hda_mixer_controls); ++i) {
snprintf(name, sizeof(name), "%s %s", cs35l56->amp_name,
cs35l56_hda_mixer_controls[i].name);
ctl_template.private_value = cs35l56_hda_mixer_controls[i].reg;
cs35l56->mixer_ctl[i] = snd_ctl_new1(&ctl_template, cs35l56);
if (snd_ctl_add(cs35l56->codec->card, cs35l56->mixer_ctl[i])) {
dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n",
ctl_template.name);
}
}
ctl_template.info = cs35l56_hda_vol_info;
ctl_template.get = cs35l56_hda_vol_get;
ctl_template.put = cs35l56_hda_vol_put;
ctl_template.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ);
ctl_template.tlv.p = cs35l56_hda_vol_tlv;
snprintf(name, sizeof(name), "%s Speaker Playback Volume", cs35l56->amp_name);
ctl_template.name = name;
cs35l56->volume_ctl = snd_ctl_new1(&ctl_template, cs35l56);
if (snd_ctl_add(cs35l56->codec->card, cs35l56->volume_ctl))
dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n", ctl_template.name);
}
static void cs35l56_hda_remove_controls(struct cs35l56_hda *cs35l56)
{
int i;
for (i = ARRAY_SIZE(cs35l56->mixer_ctl) - 1; i >= 0; i--)
snd_ctl_remove(cs35l56->codec->card, cs35l56->mixer_ctl[i]);
snd_ctl_remove(cs35l56->codec->card, cs35l56->posture_ctl);
snd_ctl_remove(cs35l56->codec->card, cs35l56->volume_ctl);
}
static const struct cs_dsp_client_ops cs35l56_hda_client_ops = {
.control_remove = hda_cs_dsp_control_remove,
};
static int cs35l56_hda_request_firmware_file(struct cs35l56_hda *cs35l56,
const struct firmware **firmware, char **filename,
const char *dir, const char *system_name,
const char *amp_name,
const char *filetype)
{
char *s, c;
int ret = 0;
if (system_name && amp_name)
*filename = kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc-%s-%s.%s", dir,
cs35l56->base.secured ? "s" : "", cs35l56->base.rev,
system_name, amp_name, filetype);
else if (system_name)
*filename = kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc-%s.%s", dir,
cs35l56->base.secured ? "s" : "", cs35l56->base.rev,
system_name, filetype);
else
*filename = kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc.%s", dir,
cs35l56->base.secured ? "s" : "", cs35l56->base.rev,
filetype);
if (!*filename)
return -ENOMEM;
/*
* Make sure that filename is lower-case and any non alpha-numeric
* characters except full stop and forward slash are replaced with
* hyphens.
*/
s = *filename;
while (*s) {
c = *s;
if (isalnum(c))
*s = tolower(c);
else if (c != '.' && c != '/')
*s = '-';
s++;
}
ret = firmware_request_nowarn(firmware, *filename, cs35l56->base.dev);
if (ret) {
dev_dbg(cs35l56->base.dev, "Failed to request '%s'\n", *filename);
kfree(*filename);
*filename = NULL;
return ret;
}
dev_dbg(cs35l56->base.dev, "Found '%s'\n", *filename);
return 0;
}
static const char cirrus_dir[] = "cirrus/";
static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56,
const struct firmware **wmfw_firmware,
char **wmfw_filename,
const struct firmware **coeff_firmware,
char **coeff_filename)
{
const char *system_name = cs35l56->system_name;
const char *amp_name = cs35l56->amp_name;
int ret;
if (system_name && amp_name) {
if (!cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
cirrus_dir, system_name, amp_name, "wmfw")) {
cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
cirrus_dir, system_name, amp_name, "bin");
return;
}
}
if (system_name) {
if (!cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
cirrus_dir, system_name, NULL, "wmfw")) {
if (amp_name)
cs35l56_hda_request_firmware_file(cs35l56,
coeff_firmware, coeff_filename,
cirrus_dir, system_name,
amp_name, "bin");
if (!*coeff_firmware)
cs35l56_hda_request_firmware_file(cs35l56,
coeff_firmware, coeff_filename,
cirrus_dir, system_name,
NULL, "bin");
return;
}
}
ret = cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename,
cirrus_dir, NULL, NULL, "wmfw");
if (!ret) {
cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
cirrus_dir, NULL, NULL, "bin");
return;
}
/* When a firmware file is not found must still search for the coeff files */
if (system_name) {
if (amp_name)
cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
cirrus_dir, system_name, amp_name, "bin");
if (!*coeff_firmware)
cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
cirrus_dir, system_name, NULL, "bin");
}
if (!*coeff_firmware)
cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename,
cirrus_dir, NULL, NULL, "bin");
}
static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmware,
char *wmfw_filename,
const struct firmware *coeff_firmware,
char *coeff_filename)
{
if (wmfw_firmware)
release_firmware(wmfw_firmware);
kfree(wmfw_filename);
if (coeff_firmware)
release_firmware(coeff_firmware);
kfree(coeff_filename);
}
static void cs35l56_hda_add_dsp_controls(struct cs35l56_hda *cs35l56)
{
struct hda_cs_dsp_ctl_info info;
info.device_name = cs35l56->amp_name;
info.fw_type = HDA_CS_DSP_FW_MISC;
info.card = cs35l56->codec->card;
hda_cs_dsp_add_controls(&cs35l56->cs_dsp, &info);
}
static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
{
const struct firmware *coeff_firmware = NULL;
const struct firmware *wmfw_firmware = NULL;
char *coeff_filename = NULL;
char *wmfw_filename = NULL;
int ret = 0;
cs35l56_hda_request_firmware_files(cs35l56, &wmfw_firmware, &wmfw_filename,
&coeff_firmware, &coeff_filename);
/* Nothing to do - no firmware files were found to download */
if (!wmfw_filename && !coeff_filename)
return 0;
mutex_lock(&cs35l56->base.irq_lock);
pm_runtime_get_sync(cs35l56->base.dev);
/*
* When the device is running in secure mode the firmware files can
* only contain insecure tunings and therefore we do not need to
* shutdown the firmware to apply them and can use the lower cost
* reinit sequence instead.
*/
if (!cs35l56->base.secured) {
ret = cs35l56_firmware_shutdown(&cs35l56->base);
if (ret)
goto err;
}
ret = cs_dsp_power_up(&cs35l56->cs_dsp, wmfw_firmware, wmfw_filename,
coeff_firmware, coeff_filename, "misc");
if (ret) {
dev_dbg(cs35l56->base.dev, "%s: cs_dsp_power_up ret %d\n", __func__, ret);
goto err;
}
if (wmfw_filename)
dev_dbg(cs35l56->base.dev, "Loaded WMFW Firmware: %s\n", wmfw_filename);
if (coeff_filename)
dev_dbg(cs35l56->base.dev, "Loaded Coefficients: %s\n", coeff_filename);
ret = cs_dsp_run(&cs35l56->cs_dsp);
if (ret) {
dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret);
goto err;
}
if (cs35l56->base.secured) {
ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
if (ret)
goto err;
} else {
/* Reset the device and wait for it to boot */
cs35l56_system_reset(&cs35l56->base, false);
ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
if (ret)
goto err;
}
/* Disable auto-hibernate so that runtime_pm has control */
ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
if (ret)
goto err;
regcache_mark_dirty(cs35l56->base.regmap);
regcache_sync(cs35l56->base.regmap);
regmap_clear_bits(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS,
CS35L56_FIRMWARE_MISSING);
cs35l56->base.fw_patched = true;
err:
pm_runtime_put(cs35l56->base.dev);
mutex_unlock(&cs35l56->base.irq_lock);
cs35l56_hda_release_firmware_files(wmfw_firmware, wmfw_filename,
coeff_firmware, coeff_filename);
return ret;
}
static int cs35l56_hda_bind(struct device *dev, struct device *master, void *master_data)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
struct hda_component *comps = master_data;
int ret;
if (!comps || cs35l56->index < 0 || cs35l56->index >= HDA_MAX_COMPONENTS)
return -EINVAL;
comps = &comps[cs35l56->index];
if (comps->dev)
return -EBUSY;
comps->dev = dev;
cs35l56->codec = comps->codec;
strscpy(comps->name, dev_name(dev), sizeof(comps->name));
comps->playback_hook = cs35l56_hda_playback_hook;
ret = cs35l56_hda_fw_load(cs35l56);
if (ret)
return ret;
cs35l56_hda_create_controls(cs35l56);
cs35l56_hda_add_dsp_controls(cs35l56);
#if IS_ENABLED(CONFIG_SND_DEBUG)
cs35l56->debugfs_root = debugfs_create_dir(dev_name(cs35l56->base.dev), sound_debugfs_root);
cs_dsp_init_debugfs(&cs35l56->cs_dsp, cs35l56->debugfs_root);
#endif
dev_dbg(cs35l56->base.dev, "Bound\n");
return 0;
}
static void cs35l56_hda_unbind(struct device *dev, struct device *master, void *master_data)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
struct hda_component *comps = master_data;
cs35l56_hda_remove_controls(cs35l56);
#if IS_ENABLED(CONFIG_SND_DEBUG)
cs_dsp_cleanup_debugfs(&cs35l56->cs_dsp);
debugfs_remove_recursive(cs35l56->debugfs_root);
#endif
cs_dsp_remove(&cs35l56->cs_dsp);
if (comps[cs35l56->index].dev == dev)
memset(&comps[cs35l56->index], 0, sizeof(*comps));
dev_dbg(cs35l56->base.dev, "Unbound\n");
}
static const struct component_ops cs35l56_hda_comp_ops = {
.bind = cs35l56_hda_bind,
.unbind = cs35l56_hda_unbind,
};
static int cs35l56_hda_system_suspend(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
if (cs35l56->playing)
cs35l56_hda_pause(cs35l56);
cs35l56->suspended = true;
/*
* The interrupt line is normally shared, but after we start suspending
* we can't check if our device is the source of an interrupt, and can't
* clear it. Prevent this race by temporarily disabling the parent irq
* until we reach _no_irq.
*/
if (cs35l56->base.irq)
disable_irq(cs35l56->base.irq);
return pm_runtime_force_suspend(dev);
}
static int cs35l56_hda_system_suspend_late(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
/*
* RESET is usually shared by all amps so it must not be asserted until
* all driver instances have done their suspend() stage.
*/
if (cs35l56->base.reset_gpio) {
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
cs35l56_wait_min_reset_pulse();
}
return 0;
}
static int cs35l56_hda_system_suspend_no_irq(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
/* Handlers are now disabled so the parent IRQ can safely be re-enabled. */
if (cs35l56->base.irq)
enable_irq(cs35l56->base.irq);
return 0;
}
static int cs35l56_hda_system_resume_no_irq(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
/*
* WAKE interrupts unmask if the CS35L56 hibernates, which can cause
* spurious interrupts, and the interrupt line is normally shared.
* We can't check if our device is the source of an interrupt, and can't
* clear it, until it has fully resumed. Prevent this race by temporarily
* disabling the parent irq until we complete resume().
*/
if (cs35l56->base.irq)
disable_irq(cs35l56->base.irq);
return 0;
}
static int cs35l56_hda_system_resume_early(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
/* Ensure a spec-compliant RESET pulse. */
if (cs35l56->base.reset_gpio) {
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
cs35l56_wait_min_reset_pulse();
/* Release shared RESET before drivers start resume(). */
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
cs35l56_wait_control_port_ready();
}
return 0;
}
static int cs35l56_hda_system_resume(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
int ret;
/* Undo pm_runtime_force_suspend() before re-enabling the irq */
ret = pm_runtime_force_resume(dev);
if (cs35l56->base.irq)
enable_irq(cs35l56->base.irq);
if (ret)
return ret;
cs35l56->suspended = false;
ret = cs35l56_is_fw_reload_needed(&cs35l56->base);
dev_dbg(cs35l56->base.dev, "fw_reload_needed: %d\n", ret);
if (ret > 0) {
ret = cs35l56_hda_fw_load(cs35l56);
if (ret)
return ret;
}
if (cs35l56->playing)
cs35l56_hda_play(cs35l56);
return 0;
}
static int cs35l56_hda_read_acpi(struct cs35l56_hda *cs35l56, int id)
{
u32 values[HDA_MAX_COMPONENTS];
struct acpi_device *adev;
const char *property, *sub;
size_t nval;
int i, ret;
/*
* ACPI_COMPANION isn't available when this driver was instantiated by
* the serial-multi-instantiate driver, so lookup the node by HID
*/
if (!ACPI_COMPANION(cs35l56->base.dev)) {
adev = acpi_dev_get_first_match_dev("CSC3556", NULL, -1);
if (!adev) {
dev_err(cs35l56->base.dev, "Failed to find an ACPI device for %s\n",
dev_name(cs35l56->base.dev));
return -ENODEV;
}
ACPI_COMPANION_SET(cs35l56->base.dev, adev);
}
property = "cirrus,dev-index";
ret = device_property_count_u32(cs35l56->base.dev, property);
if (ret <= 0)
goto err;
if (ret > ARRAY_SIZE(values)) {
ret = -EINVAL;
goto err;
}
nval = ret;
ret = device_property_read_u32_array(cs35l56->base.dev, property, values, nval);
if (ret)
goto err;
cs35l56->index = -1;
for (i = 0; i < nval; i++) {
if (values[i] == id) {
cs35l56->index = i;
break;
}
}
if (cs35l56->index == -1) {
dev_err(cs35l56->base.dev, "No index found in %s\n", property);
ret = -ENODEV;
goto err;
}
sub = acpi_get_subsystem_id(ACPI_HANDLE(cs35l56->base.dev));
if (IS_ERR(sub)) {
/* If no ACPI SUB, return 0 and fallback to legacy firmware path, otherwise fail */
if (PTR_ERR(sub) == -ENODATA)
return 0;
else
return PTR_ERR(sub);
}
cs35l56->system_name = sub;
cs35l56->base.reset_gpio = devm_gpiod_get_index_optional(cs35l56->base.dev,
"reset",
cs35l56->index,
GPIOD_OUT_LOW);
if (IS_ERR(cs35l56->base.reset_gpio)) {
ret = PTR_ERR(cs35l56->base.reset_gpio);
/*
* If RESET is shared the first amp to probe will grab the reset
* line and reset all the amps
*/
if (ret != -EBUSY)
return dev_err_probe(cs35l56->base.dev, ret, "Failed to get reset GPIO\n");
dev_info(cs35l56->base.dev, "Reset GPIO busy, assume shared reset\n");
cs35l56->base.reset_gpio = NULL;
}
return 0;
err:
dev_err(cs35l56->base.dev, "Failed property %s: %d\n", property, ret);
return ret;
}
int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id)
{
int ret;
mutex_init(&cs35l56->base.irq_lock);
dev_set_drvdata(cs35l56->base.dev, cs35l56);
ret = cs35l56_hda_read_acpi(cs35l56, id);
if (ret) {
dev_err_probe(cs35l56->base.dev, ret, "Platform not supported\n");
goto err;
}
cs35l56->amp_name = devm_kasprintf(cs35l56->base.dev, GFP_KERNEL, "AMP%d",
cs35l56->index + 1);
if (!cs35l56->amp_name) {
ret = -ENOMEM;
goto err;
}
cs35l56_init_cs_dsp(&cs35l56->base, &cs35l56->cs_dsp);
cs35l56->cs_dsp.client_ops = &cs35l56_hda_client_ops;
if (cs35l56->base.reset_gpio) {
dev_dbg(cs35l56->base.dev, "Hard reset\n");
/*
* The GPIOD_OUT_LOW to *_gpiod_get_*() will be ignored if the
* ACPI defines a different default state. So explicitly set low.
*/
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
cs35l56_wait_min_reset_pulse();
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
}
ret = cs35l56_hw_init(&cs35l56->base);
if (ret < 0)
goto err;
/* Reset the device and wait for it to boot */
cs35l56_system_reset(&cs35l56->base, false);
ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
if (ret)
goto err;
ret = cs35l56_set_patch(&cs35l56->base);
if (ret)
return ret;
regcache_mark_dirty(cs35l56->base.regmap);
regcache_sync(cs35l56->base.regmap);
/* Disable auto-hibernate so that runtime_pm has control */
ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
if (ret)
goto err;
ret = cs_dsp_halo_init(&cs35l56->cs_dsp);
if (ret) {
dev_err_probe(cs35l56->base.dev, ret, "cs_dsp_halo_init failed\n");
goto err;
}
dev_dbg(cs35l56->base.dev, "DSP system name: '%s', amp name: '%s'\n",
cs35l56->system_name, cs35l56->amp_name);
regmap_multi_reg_write(cs35l56->base.regmap, cs35l56_hda_dai_config,
ARRAY_SIZE(cs35l56_hda_dai_config));
/*
* By default only enable one ASP1TXn, where n=amplifier index,
* This prevents multiple amps trying to drive the same slot.
*/
cs35l56->asp_tx_mask = BIT(cs35l56->index);
pm_runtime_set_autosuspend_delay(cs35l56->base.dev, 3000);
pm_runtime_use_autosuspend(cs35l56->base.dev);
pm_runtime_set_active(cs35l56->base.dev);
pm_runtime_mark_last_busy(cs35l56->base.dev);
pm_runtime_enable(cs35l56->base.dev);
ret = component_add(cs35l56->base.dev, &cs35l56_hda_comp_ops);
if (ret) {
dev_err(cs35l56->base.dev, "Register component failed: %d\n", ret);
goto pm_err;
}
cs35l56->base.init_done = true;
return 0;
pm_err:
pm_runtime_disable(cs35l56->base.dev);
err:
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
return ret;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_hda_common_probe, SND_HDA_SCODEC_CS35L56);
void cs35l56_hda_remove(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
pm_runtime_get_sync(cs35l56->base.dev);
pm_runtime_disable(cs35l56->base.dev);
component_del(cs35l56->base.dev, &cs35l56_hda_comp_ops);
kfree(cs35l56->system_name);
pm_runtime_put_noidle(cs35l56->base.dev);
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
}
EXPORT_SYMBOL_NS_GPL(cs35l56_hda_remove, SND_HDA_SCODEC_CS35L56);
const struct dev_pm_ops cs35l56_hda_pm_ops = {
SET_RUNTIME_PM_OPS(cs35l56_hda_runtime_suspend, cs35l56_hda_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend, cs35l56_hda_system_resume)
LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend_late,
cs35l56_hda_system_resume_early)
NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend_no_irq,
cs35l56_hda_system_resume_no_irq)
};
EXPORT_SYMBOL_NS_GPL(cs35l56_hda_pm_ops, SND_HDA_SCODEC_CS35L56);
MODULE_DESCRIPTION("CS35L56 HDA Driver");
MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS);
MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(FW_CS_DSP);

View File

@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* HDA audio driver for Cirrus Logic CS35L56 smart amp
*
* Copyright (C) 2023 Cirrus Logic, Inc. and
* Cirrus Logic International Semiconductor Ltd.
*/
#ifndef __CS35L56_HDA_H__
#define __CS35L56_HDA_H__
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/firmware/cirrus/cs_dsp.h>
#include <linux/firmware/cirrus/wmfw.h>
#include <linux/regulator/consumer.h>
#include <sound/cs35l56.h>
struct dentry;
struct cs35l56_hda {
struct cs35l56_base base;
struct hda_codec *codec;
int index;
const char *system_name;
const char *amp_name;
struct cs_dsp cs_dsp;
bool playing;
bool suspended;
u8 asp_tx_mask;
struct snd_kcontrol *posture_ctl;
struct snd_kcontrol *volume_ctl;
struct snd_kcontrol *mixer_ctl[4];
#if IS_ENABLED(CONFIG_SND_DEBUG)
struct dentry *debugfs_root;
#endif
};
extern const struct dev_pm_ops cs35l56_hda_pm_ops;
int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id);
void cs35l56_hda_remove(struct device *dev);
#endif /*__CS35L56_HDA_H__*/

View File

@ -0,0 +1,69 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// CS35L56 HDA audio driver I2C binding
//
// Copyright (C) 2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include "cs35l56_hda.h"
static int cs35l56_hda_i2c_probe(struct i2c_client *clt)
{
struct cs35l56_hda *cs35l56;
int ret;
cs35l56 = devm_kzalloc(&clt->dev, sizeof(*cs35l56), GFP_KERNEL);
if (!cs35l56)
return -ENOMEM;
cs35l56->base.dev = &clt->dev;
cs35l56->base.can_hibernate = true;
cs35l56->base.regmap = devm_regmap_init_i2c(clt, &cs35l56_regmap_i2c);
if (IS_ERR(cs35l56->base.regmap)) {
ret = PTR_ERR(cs35l56->base.regmap);
dev_err(cs35l56->base.dev, "Failed to allocate register map: %d\n",
ret);
return ret;
}
ret = cs35l56_hda_common_probe(cs35l56, clt->addr);
if (ret)
return ret;
ret = cs35l56_irq_request(&cs35l56->base, clt->irq);
if (ret < 0)
cs35l56_hda_remove(cs35l56->base.dev);
return ret;
}
static void cs35l56_hda_i2c_remove(struct i2c_client *clt)
{
cs35l56_hda_remove(&clt->dev);
}
static const struct i2c_device_id cs35l56_hda_i2c_id[] = {
{ "cs35l56-hda", 0 },
{}
};
static struct i2c_driver cs35l56_hda_i2c_driver = {
.driver = {
.name = "cs35l56-hda",
.pm = &cs35l56_hda_pm_ops,
},
.id_table = cs35l56_hda_i2c_id,
.probe = cs35l56_hda_i2c_probe,
.remove = cs35l56_hda_i2c_remove,
};
module_i2c_driver(cs35l56_hda_i2c_driver);
MODULE_DESCRIPTION("HDA CS35L56 I2C driver");
MODULE_IMPORT_NS(SND_HDA_SCODEC_CS35L56);
MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// CS35L56 HDA audio driver SPI binding
//
// Copyright (C) 2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include "cs35l56_hda.h"
static int cs35l56_hda_spi_probe(struct spi_device *spi)
{
struct cs35l56_hda *cs35l56;
int ret;
cs35l56 = devm_kzalloc(&spi->dev, sizeof(*cs35l56), GFP_KERNEL);
if (!cs35l56)
return -ENOMEM;
cs35l56->base.dev = &spi->dev;
cs35l56->base.regmap = devm_regmap_init_spi(spi, &cs35l56_regmap_spi);
if (IS_ERR(cs35l56->base.regmap)) {
ret = PTR_ERR(cs35l56->base.regmap);
dev_err(cs35l56->base.dev, "Failed to allocate register map: %d\n",
ret);
return ret;
}
ret = cs35l56_hda_common_probe(cs35l56, spi->chip_select);
if (ret)
return ret;
ret = cs35l56_irq_request(&cs35l56->base, spi->irq);
if (ret < 0)
cs35l56_hda_remove(cs35l56->base.dev);
return ret;
}
static void cs35l56_hda_spi_remove(struct spi_device *spi)
{
cs35l56_hda_remove(&spi->dev);
}
static const struct spi_device_id cs35l56_hda_spi_id[] = {
{ "cs35l56-hda", 0 },
{}
};
static struct spi_driver cs35l56_hda_spi_driver = {
.driver = {
.name = "cs35l56-hda",
.pm = &cs35l56_hda_pm_ops,
},
.id_table = cs35l56_hda_spi_id,
.probe = cs35l56_hda_spi_probe,
.remove = cs35l56_hda_spi_remove,
};
module_spi_driver(cs35l56_hda_spi_driver);
MODULE_DESCRIPTION("HDA CS35L56 SPI driver");
MODULE_IMPORT_NS(SND_HDA_SCODEC_CS35L56);
MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
MODULE_LICENSE("GPL");

View File

@ -8,6 +8,8 @@
#ifndef __SOUND_HDA_AUTO_PARSER_H #ifndef __SOUND_HDA_AUTO_PARSER_H
#define __SOUND_HDA_AUTO_PARSER_H #define __SOUND_HDA_AUTO_PARSER_H
#include "hda_local.h"
/* /*
* Helper for automatic pin configuration * Helper for automatic pin configuration
*/ */

View File

@ -9,6 +9,9 @@
#define __SOUND_HDA_GENERIC_H #define __SOUND_HDA_GENERIC_H
#include <linux/leds.h> #include <linux/leds.h>
#include "hda_auto_parser.h"
struct hda_jack_callback;
/* table entry for multi-io paths */ /* table entry for multi-io paths */
struct hda_multi_io { struct hda_multi_io {

View File

@ -26,14 +26,14 @@ static int cs35l56_i2c_probe(struct i2c_client *client)
if (!cs35l56) if (!cs35l56)
return -ENOMEM; return -ENOMEM;
cs35l56->dev = dev; cs35l56->base.dev = dev;
cs35l56->can_hibernate = true; cs35l56->base.can_hibernate = true;
i2c_set_clientdata(client, cs35l56); i2c_set_clientdata(client, cs35l56);
cs35l56->regmap = devm_regmap_init_i2c(client, regmap_config); cs35l56->base.regmap = devm_regmap_init_i2c(client, regmap_config);
if (IS_ERR(cs35l56->regmap)) { if (IS_ERR(cs35l56->base.regmap)) {
ret = PTR_ERR(cs35l56->regmap); ret = PTR_ERR(cs35l56->base.regmap);
return dev_err_probe(cs35l56->dev, ret, "Failed to allocate register map\n"); return dev_err_probe(cs35l56->base.dev, ret, "Failed to allocate register map\n");
} }
ret = cs35l56_common_probe(cs35l56); ret = cs35l56_common_probe(cs35l56);
@ -42,7 +42,7 @@ static int cs35l56_i2c_probe(struct i2c_client *client)
ret = cs35l56_init(cs35l56); ret = cs35l56_init(cs35l56);
if (ret == 0) if (ret == 0)
ret = cs35l56_irq_request(cs35l56, client->irq); ret = cs35l56_irq_request(&cs35l56->base, client->irq);
if (ret < 0) if (ret < 0)
cs35l56_remove(cs35l56); cs35l56_remove(cs35l56);

View File

@ -166,13 +166,13 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral)
struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
int ret; int ret;
pm_runtime_get_noresume(cs35l56->dev); pm_runtime_get_noresume(cs35l56->base.dev);
regcache_cache_only(cs35l56->regmap, false); regcache_cache_only(cs35l56->base.regmap, false);
ret = cs35l56_init(cs35l56); ret = cs35l56_init(cs35l56);
if (ret < 0) { if (ret < 0) {
regcache_cache_only(cs35l56->regmap, true); regcache_cache_only(cs35l56->base.regmap, true);
goto out; goto out;
} }
@ -180,15 +180,15 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral)
* cs35l56_init can return with !init_done if it triggered * cs35l56_init can return with !init_done if it triggered
* a soft reset. * a soft reset.
*/ */
if (cs35l56->init_done) { if (cs35l56->base.init_done) {
/* Enable SoundWire interrupts */ /* Enable SoundWire interrupts */
sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1,
CS35L56_SDW_INT_MASK_CODEC_IRQ); CS35L56_SDW_INT_MASK_CODEC_IRQ);
} }
out: out:
pm_runtime_mark_last_busy(cs35l56->dev); pm_runtime_mark_last_busy(cs35l56->base.dev);
pm_runtime_put_autosuspend(cs35l56->dev); pm_runtime_put_autosuspend(cs35l56->base.dev);
} }
static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral, static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral,
@ -198,7 +198,7 @@ static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral,
/* SoundWire core holds our pm_runtime when calling this function. */ /* SoundWire core holds our pm_runtime when calling this function. */
dev_dbg(cs35l56->dev, "int control_port=%#x\n", status->control_port); dev_dbg(cs35l56->base.dev, "int control_port=%#x\n", status->control_port);
if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0) if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0)
return 0; return 0;
@ -207,7 +207,7 @@ static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral,
* Prevent bus manager suspending and possibly issuing a * Prevent bus manager suspending and possibly issuing a
* bus-reset before the queued work has run. * bus-reset before the queued work has run.
*/ */
pm_runtime_get_noresume(cs35l56->dev); pm_runtime_get_noresume(cs35l56->base.dev);
/* /*
* Mask and clear until it has been handled. The read of GEN_INT_STAT_1 * Mask and clear until it has been handled. The read of GEN_INT_STAT_1
@ -230,14 +230,14 @@ static void cs35l56_sdw_irq_work(struct work_struct *work)
struct cs35l56_private, struct cs35l56_private,
sdw_irq_work); sdw_irq_work);
cs35l56_irq(-1, cs35l56); cs35l56_irq(-1, &cs35l56->base);
/* unmask interrupts */ /* unmask interrupts */
if (!cs35l56->sdw_irq_no_unmask) if (!cs35l56->sdw_irq_no_unmask)
sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1,
CS35L56_SDW_INT_MASK_CODEC_IRQ); CS35L56_SDW_INT_MASK_CODEC_IRQ);
pm_runtime_put_autosuspend(cs35l56->dev); pm_runtime_put_autosuspend(cs35l56->base.dev);
} }
static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral)
@ -246,7 +246,7 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral)
struct sdw_slave_prop *prop = &peripheral->prop; struct sdw_slave_prop *prop = &peripheral->prop;
struct sdw_dpn_prop *ports; struct sdw_dpn_prop *ports;
ports = devm_kcalloc(cs35l56->dev, 2, sizeof(*ports), GFP_KERNEL); ports = devm_kcalloc(cs35l56->base.dev, 2, sizeof(*ports), GFP_KERNEL);
if (!ports) if (!ports)
return -ENOMEM; return -ENOMEM;
@ -279,17 +279,17 @@ static int cs35l56_sdw_update_status(struct sdw_slave *peripheral,
switch (status) { switch (status) {
case SDW_SLAVE_ATTACHED: case SDW_SLAVE_ATTACHED:
dev_dbg(cs35l56->dev, "%s: ATTACHED\n", __func__); dev_dbg(cs35l56->base.dev, "%s: ATTACHED\n", __func__);
if (cs35l56->sdw_attached) if (cs35l56->sdw_attached)
break; break;
if (!cs35l56->init_done || cs35l56->soft_resetting) if (!cs35l56->base.init_done || cs35l56->soft_resetting)
cs35l56_sdw_init(peripheral); cs35l56_sdw_init(peripheral);
cs35l56->sdw_attached = true; cs35l56->sdw_attached = true;
break; break;
case SDW_SLAVE_UNATTACHED: case SDW_SLAVE_UNATTACHED:
dev_dbg(cs35l56->dev, "%s: UNATTACHED\n", __func__); dev_dbg(cs35l56->base.dev, "%s: UNATTACHED\n", __func__);
cs35l56->sdw_attached = false; cs35l56->sdw_attached = false;
break; break;
default: default:
@ -305,7 +305,7 @@ static int cs35l56_a1_kick_divider(struct cs35l56_private *cs35l56,
unsigned int curr_scale_reg, next_scale_reg; unsigned int curr_scale_reg, next_scale_reg;
int curr_scale, next_scale, ret; int curr_scale, next_scale, ret;
if (!cs35l56->init_done) if (!cs35l56->base.init_done)
return 0; return 0;
if (peripheral->bus->params.curr_bank) { if (peripheral->bus->params.curr_bank) {
@ -324,13 +324,13 @@ static int cs35l56_a1_kick_divider(struct cs35l56_private *cs35l56,
*/ */
curr_scale = sdw_read_no_pm(peripheral, curr_scale_reg); curr_scale = sdw_read_no_pm(peripheral, curr_scale_reg);
if (curr_scale < 0) { if (curr_scale < 0) {
dev_err(cs35l56->dev, "Failed to read current clock scale: %d\n", curr_scale); dev_err(cs35l56->base.dev, "Failed to read current clock scale: %d\n", curr_scale);
return curr_scale; return curr_scale;
} }
next_scale = sdw_read_no_pm(peripheral, next_scale_reg); next_scale = sdw_read_no_pm(peripheral, next_scale_reg);
if (next_scale < 0) { if (next_scale < 0) {
dev_err(cs35l56->dev, "Failed to read next clock scale: %d\n", next_scale); dev_err(cs35l56->base.dev, "Failed to read next clock scale: %d\n", next_scale);
return next_scale; return next_scale;
} }
@ -338,7 +338,8 @@ static int cs35l56_a1_kick_divider(struct cs35l56_private *cs35l56,
next_scale = cs35l56->old_sdw_clock_scale; next_scale = cs35l56->old_sdw_clock_scale;
ret = sdw_write_no_pm(peripheral, next_scale_reg, next_scale); ret = sdw_write_no_pm(peripheral, next_scale_reg, next_scale);
if (ret < 0) { if (ret < 0) {
dev_err(cs35l56->dev, "Failed to modify current clock scale: %d\n", ret); dev_err(cs35l56->base.dev, "Failed to modify current clock scale: %d\n",
ret);
return ret; return ret;
} }
} }
@ -346,11 +347,11 @@ static int cs35l56_a1_kick_divider(struct cs35l56_private *cs35l56,
cs35l56->old_sdw_clock_scale = curr_scale; cs35l56->old_sdw_clock_scale = curr_scale;
ret = sdw_write_no_pm(peripheral, curr_scale_reg, CS35L56_SDW_INVALID_BUS_SCALE); ret = sdw_write_no_pm(peripheral, curr_scale_reg, CS35L56_SDW_INVALID_BUS_SCALE);
if (ret < 0) { if (ret < 0) {
dev_err(cs35l56->dev, "Failed to modify current clock scale: %d\n", ret); dev_err(cs35l56->base.dev, "Failed to modify current clock scale: %d\n", ret);
return ret; return ret;
} }
dev_dbg(cs35l56->dev, "Next bus scale: %#x\n", next_scale); dev_dbg(cs35l56->base.dev, "Next bus scale: %#x\n", next_scale);
return 0; return 0;
} }
@ -362,9 +363,10 @@ static int cs35l56_sdw_bus_config(struct sdw_slave *peripheral,
int sclk; int sclk;
sclk = params->curr_dr_freq / 2; sclk = params->curr_dr_freq / 2;
dev_dbg(cs35l56->dev, "%s: sclk=%u c=%u r=%u\n", __func__, sclk, params->col, params->row); dev_dbg(cs35l56->base.dev, "%s: sclk=%u c=%u r=%u\n",
__func__, sclk, params->col, params->row);
if (cs35l56->rev < 0xb0) if (cs35l56->base.rev < 0xb0)
return cs35l56_a1_kick_divider(cs35l56, peripheral); return cs35l56_a1_kick_divider(cs35l56, peripheral);
return 0; return 0;
@ -376,7 +378,7 @@ static int __maybe_unused cs35l56_sdw_clk_stop(struct sdw_slave *peripheral,
{ {
struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
dev_dbg(cs35l56->dev, "%s: mode:%d type:%d\n", __func__, mode, type); dev_dbg(cs35l56->base.dev, "%s: mode:%d type:%d\n", __func__, mode, type);
return 0; return 0;
} }
@ -397,10 +399,10 @@ static int __maybe_unused cs35l56_sdw_handle_unattach(struct cs35l56_private *cs
if (peripheral->unattach_request) { if (peripheral->unattach_request) {
/* Cannot access registers until bus is re-initialized. */ /* Cannot access registers until bus is re-initialized. */
dev_dbg(cs35l56->dev, "Wait for initialization_complete\n"); dev_dbg(cs35l56->base.dev, "Wait for initialization_complete\n");
if (!wait_for_completion_timeout(&peripheral->initialization_complete, if (!wait_for_completion_timeout(&peripheral->initialization_complete,
msecs_to_jiffies(5000))) { msecs_to_jiffies(5000))) {
dev_err(cs35l56->dev, "initialization_complete timed out\n"); dev_err(cs35l56->base.dev, "initialization_complete timed out\n");
return -ETIMEDOUT; return -ETIMEDOUT;
} }
@ -419,10 +421,10 @@ static int __maybe_unused cs35l56_sdw_runtime_suspend(struct device *dev)
{ {
struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
if (!cs35l56->init_done) if (!cs35l56->base.init_done)
return 0; return 0;
return cs35l56_runtime_suspend(dev); return cs35l56_runtime_suspend_common(&cs35l56->base);
} }
static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev) static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev)
@ -432,14 +434,14 @@ static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev)
dev_dbg(dev, "Runtime resume\n"); dev_dbg(dev, "Runtime resume\n");
if (!cs35l56->init_done) if (!cs35l56->base.init_done)
return 0; return 0;
ret = cs35l56_sdw_handle_unattach(cs35l56); ret = cs35l56_sdw_handle_unattach(cs35l56);
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = cs35l56_runtime_resume_common(cs35l56); ret = cs35l56_runtime_resume_common(&cs35l56->base, true);
if (ret) if (ret)
return ret; return ret;
@ -454,7 +456,7 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev)
{ {
struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
if (!cs35l56->init_done) if (!cs35l56->base.init_done)
return 0; return 0;
/* /*
@ -493,21 +495,21 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi
if (!cs35l56) if (!cs35l56)
return -ENOMEM; return -ENOMEM;
cs35l56->dev = dev; cs35l56->base.dev = dev;
cs35l56->sdw_peripheral = peripheral; cs35l56->sdw_peripheral = peripheral;
INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work); INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work);
dev_set_drvdata(dev, cs35l56); dev_set_drvdata(dev, cs35l56);
cs35l56->regmap = devm_regmap_init(dev, &cs35l56_regmap_bus_sdw, cs35l56->base.regmap = devm_regmap_init(dev, &cs35l56_regmap_bus_sdw,
peripheral, &cs35l56_regmap_sdw); peripheral, &cs35l56_regmap_sdw);
if (IS_ERR(cs35l56->regmap)) { if (IS_ERR(cs35l56->base.regmap)) {
ret = PTR_ERR(cs35l56->regmap); ret = PTR_ERR(cs35l56->base.regmap);
return dev_err_probe(dev, ret, "Failed to allocate register map\n"); return dev_err_probe(dev, ret, "Failed to allocate register map\n");
} }
/* Start in cache-only until device is enumerated */ /* Start in cache-only until device is enumerated */
regcache_cache_only(cs35l56->regmap, true); regcache_cache_only(cs35l56->base.regmap, true);
ret = cs35l56_common_probe(cs35l56); ret = cs35l56_common_probe(cs35l56);
if (ret != 0) if (ret != 0)

View File

@ -18,9 +18,10 @@ static const struct reg_sequence cs35l56_patch[] = {
{ CS35L56_MAIN_POSTURE_NUMBER, 0x00000000 }, { CS35L56_MAIN_POSTURE_NUMBER, 0x00000000 },
}; };
int cs35l56_set_patch(struct regmap *regmap) int cs35l56_set_patch(struct cs35l56_base *cs35l56_base)
{ {
return regmap_register_patch(regmap, cs35l56_patch, ARRAY_SIZE(cs35l56_patch)); return regmap_register_patch(cs35l56_base->regmap, cs35l56_patch,
ARRAY_SIZE(cs35l56_patch));
} }
EXPORT_SYMBOL_NS_GPL(cs35l56_set_patch, SND_SOC_CS35L56_SHARED); EXPORT_SYMBOL_NS_GPL(cs35l56_set_patch, SND_SOC_CS35L56_SHARED);
@ -194,14 +195,463 @@ static bool cs35l56_volatile_reg(struct device *dev, unsigned int reg)
} }
} }
const struct cs_dsp_region cs35l56_dsp1_regions[] = { int cs35l56_mbox_send(struct cs35l56_base *cs35l56_base, unsigned int command)
{
unsigned int val;
int ret;
regmap_write(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, command);
ret = regmap_read_poll_timeout(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
val, (val == 0),
CS35L56_MBOX_POLL_US, CS35L56_MBOX_TIMEOUT_US);
if (ret) {
dev_warn(cs35l56_base->dev, "MBOX command %#x failed: %d\n", command, ret);
return ret;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_mbox_send, SND_SOC_CS35L56_SHARED);
int cs35l56_firmware_shutdown(struct cs35l56_base *cs35l56_base)
{
int ret;
unsigned int reg;
unsigned int val;
ret = cs35l56_mbox_send(cs35l56_base, CS35L56_MBOX_CMD_SHUTDOWN);
if (ret)
return ret;
if (cs35l56_base->rev < CS35L56_REVID_B0)
reg = CS35L56_DSP1_PM_CUR_STATE_A1;
else
reg = CS35L56_DSP1_PM_CUR_STATE;
ret = regmap_read_poll_timeout(cs35l56_base->regmap, reg,
val, (val == CS35L56_HALO_STATE_SHUTDOWN),
CS35L56_HALO_STATE_POLL_US,
CS35L56_HALO_STATE_TIMEOUT_US);
if (ret < 0)
dev_err(cs35l56_base->dev, "Failed to poll PM_CUR_STATE to 1 is %d (ret %d)\n",
val, ret);
return ret;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_firmware_shutdown, SND_SOC_CS35L56_SHARED);
int cs35l56_wait_for_firmware_boot(struct cs35l56_base *cs35l56_base)
{
unsigned int reg;
unsigned int val;
int ret;
if (cs35l56_base->rev < CS35L56_REVID_B0)
reg = CS35L56_DSP1_HALO_STATE_A1;
else
reg = CS35L56_DSP1_HALO_STATE;
ret = regmap_read_poll_timeout(cs35l56_base->regmap, reg,
val,
(val < 0xFFFF) && (val >= CS35L56_HALO_STATE_BOOT_DONE),
CS35L56_HALO_STATE_POLL_US,
CS35L56_HALO_STATE_TIMEOUT_US);
if ((ret < 0) && (ret != -ETIMEDOUT)) {
dev_err(cs35l56_base->dev, "Failed to read HALO_STATE: %d\n", ret);
return ret;
}
if ((ret == -ETIMEDOUT) || (val != CS35L56_HALO_STATE_BOOT_DONE)) {
dev_err(cs35l56_base->dev, "Firmware boot fail: HALO_STATE=%#x\n", val);
return -EIO;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_wait_for_firmware_boot, SND_SOC_CS35L56_SHARED);
void cs35l56_wait_control_port_ready(void)
{
/* Wait for control port to be ready (datasheet tIRS). */
usleep_range(CS35L56_CONTROL_PORT_READY_US, 2 * CS35L56_CONTROL_PORT_READY_US);
}
EXPORT_SYMBOL_NS_GPL(cs35l56_wait_control_port_ready, SND_SOC_CS35L56_SHARED);
void cs35l56_wait_min_reset_pulse(void)
{
/* Satisfy minimum reset pulse width spec */
usleep_range(CS35L56_RESET_PULSE_MIN_US, 2 * CS35L56_RESET_PULSE_MIN_US);
}
EXPORT_SYMBOL_NS_GPL(cs35l56_wait_min_reset_pulse, SND_SOC_CS35L56_SHARED);
static const struct reg_sequence cs35l56_system_reset_seq[] = {
REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_SYSTEM_RESET),
};
void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire)
{
/*
* Must enter cache-only first so there can't be any more register
* accesses other than the controlled system reset sequence below.
*/
regcache_cache_only(cs35l56_base->regmap, true);
regmap_multi_reg_write_bypassed(cs35l56_base->regmap,
cs35l56_system_reset_seq,
ARRAY_SIZE(cs35l56_system_reset_seq));
/* On SoundWire the registers won't be accessible until it re-enumerates. */
if (is_soundwire)
return;
cs35l56_wait_control_port_ready();
regcache_cache_only(cs35l56_base->regmap, false);
}
EXPORT_SYMBOL_NS_GPL(cs35l56_system_reset, SND_SOC_CS35L56_SHARED);
int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq)
{
int ret;
if (!irq)
return 0;
ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq,
IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW,
"cs35l56", cs35l56_base);
if (!ret)
cs35l56_base->irq = irq;
else
dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret);
return ret;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, SND_SOC_CS35L56_SHARED);
irqreturn_t cs35l56_irq(int irq, void *data)
{
struct cs35l56_base *cs35l56_base = data;
unsigned int status1 = 0, status8 = 0, status20 = 0;
unsigned int mask1, mask8, mask20;
unsigned int val;
int rv;
irqreturn_t ret = IRQ_NONE;
if (!cs35l56_base->init_done)
return IRQ_NONE;
mutex_lock(&cs35l56_base->irq_lock);
rv = pm_runtime_resume_and_get(cs35l56_base->dev);
if (rv < 0) {
dev_err(cs35l56_base->dev, "irq: failed to get pm_runtime: %d\n", rv);
goto err_unlock;
}
regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_STATUS, &val);
if ((val & CS35L56_IRQ1_STS_MASK) == 0) {
dev_dbg(cs35l56_base->dev, "Spurious IRQ: no pending interrupt\n");
goto err;
}
/* Ack interrupts */
regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_1, &status1);
regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_1, &mask1);
status1 &= ~mask1;
regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_1, status1);
regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_8, &status8);
regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_8, &mask8);
status8 &= ~mask8;
regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_8, status8);
regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_20, &status20);
regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, &mask20);
status20 &= ~mask20;
/* We don't want EINT20 but they default to unmasked: force mask */
regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
dev_dbg(cs35l56_base->dev, "%s: %#x %#x\n", __func__, status1, status8);
/* Check to see if unmasked bits are active */
if (!status1 && !status8 && !status20)
goto err;
if (status1 & CS35L56_AMP_SHORT_ERR_EINT1_MASK)
dev_crit(cs35l56_base->dev, "Amp short error\n");
if (status8 & CS35L56_TEMP_ERR_EINT1_MASK)
dev_crit(cs35l56_base->dev, "Overtemp error\n");
ret = IRQ_HANDLED;
err:
pm_runtime_put(cs35l56_base->dev);
err_unlock:
mutex_unlock(&cs35l56_base->irq_lock);
return ret;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_irq, SND_SOC_CS35L56_SHARED);
int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base)
{
unsigned int val;
int ret;
/* Nothing to re-patch if we haven't done any patching yet. */
if (!cs35l56_base->fw_patched)
return false;
/*
* If we have control of RESET we will have asserted it so the firmware
* will need re-patching.
*/
if (cs35l56_base->reset_gpio)
return true;
/*
* In secure mode FIRMWARE_MISSING is cleared by the BIOS loader so
* can't be used here to test for memory retention.
* Assume that tuning must be re-loaded.
*/
if (cs35l56_base->secured)
return true;
ret = pm_runtime_resume_and_get(cs35l56_base->dev);
if (ret) {
dev_err(cs35l56_base->dev, "Failed to runtime_get: %d\n", ret);
return ret;
}
ret = regmap_read(cs35l56_base->regmap, CS35L56_PROTECTION_STATUS, &val);
if (ret)
dev_err(cs35l56_base->dev, "Failed to read PROTECTION_STATUS: %d\n", ret);
else
ret = !!(val & CS35L56_FIRMWARE_MISSING);
pm_runtime_put_autosuspend(cs35l56_base->dev);
return ret;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_is_fw_reload_needed, SND_SOC_CS35L56_SHARED);
static const struct reg_sequence cs35l56_hibernate_seq[] = {
/* This must be the last register access */
REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_HIBERNATE_NOW),
};
static const struct reg_sequence cs35l56_hibernate_wake_seq[] = {
REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_WAKEUP),
};
int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base)
{
unsigned int val;
int ret;
if (!cs35l56_base->init_done)
return 0;
/* Firmware must have entered a power-save state */
ret = regmap_read_poll_timeout(cs35l56_base->regmap,
CS35L56_TRANSDUCER_ACTUAL_PS,
val, (val >= CS35L56_PS3),
CS35L56_PS3_POLL_US,
CS35L56_PS3_TIMEOUT_US);
if (ret)
dev_warn(cs35l56_base->dev, "PS3 wait failed: %d\n", ret);
/* Clear BOOT_DONE so it can be used to detect a reboot */
regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_4, CS35L56_OTP_BOOT_DONE_MASK);
if (!cs35l56_base->can_hibernate) {
regcache_cache_only(cs35l56_base->regmap, true);
dev_dbg(cs35l56_base->dev, "Suspended: no hibernate");
return 0;
}
/*
* Enable auto-hibernate. If it is woken by some other wake source
* it will automatically return to hibernate.
*/
cs35l56_mbox_send(cs35l56_base, CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE);
/*
* Must enter cache-only first so there can't be any more register
* accesses other than the controlled hibernate sequence below.
*/
regcache_cache_only(cs35l56_base->regmap, true);
regmap_multi_reg_write_bypassed(cs35l56_base->regmap,
cs35l56_hibernate_seq,
ARRAY_SIZE(cs35l56_hibernate_seq));
dev_dbg(cs35l56_base->dev, "Suspended: hibernate");
return 0;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_suspend_common, SND_SOC_CS35L56_SHARED);
int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire)
{
unsigned int val;
int ret;
if (!cs35l56_base->init_done)
return 0;
if (!cs35l56_base->can_hibernate)
goto out_sync;
if (!is_soundwire) {
/*
* Dummy transaction to trigger I2C/SPI auto-wake. This will NAK on I2C.
* Must be done before releasing cache-only.
*/
regmap_multi_reg_write_bypassed(cs35l56_base->regmap,
cs35l56_hibernate_wake_seq,
ARRAY_SIZE(cs35l56_hibernate_wake_seq));
cs35l56_wait_control_port_ready();
}
out_sync:
regcache_cache_only(cs35l56_base->regmap, false);
ret = cs35l56_wait_for_firmware_boot(cs35l56_base);
if (ret) {
dev_err(cs35l56_base->dev, "Hibernate wake failed: %d\n", ret);
goto err;
}
ret = cs35l56_mbox_send(cs35l56_base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
if (ret)
goto err;
/* BOOT_DONE will be 1 if the amp reset */
regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_EINT_4, &val);
if (val & CS35L56_OTP_BOOT_DONE_MASK) {
dev_dbg(cs35l56_base->dev, "Registers reset in suspend\n");
regcache_mark_dirty(cs35l56_base->regmap);
}
regcache_sync(cs35l56_base->regmap);
dev_dbg(cs35l56_base->dev, "Resumed");
return 0;
err:
regmap_write(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
CS35L56_MBOX_CMD_HIBERNATE_NOW);
regcache_cache_only(cs35l56_base->regmap, true);
return ret;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_resume_common, SND_SOC_CS35L56_SHARED);
static const struct cs_dsp_region cs35l56_dsp1_regions[] = {
{ .type = WMFW_HALO_PM_PACKED, .base = CS35L56_DSP1_PMEM_0 }, { .type = WMFW_HALO_PM_PACKED, .base = CS35L56_DSP1_PMEM_0 },
{ .type = WMFW_HALO_XM_PACKED, .base = CS35L56_DSP1_XMEM_PACKED_0 }, { .type = WMFW_HALO_XM_PACKED, .base = CS35L56_DSP1_XMEM_PACKED_0 },
{ .type = WMFW_HALO_YM_PACKED, .base = CS35L56_DSP1_YMEM_PACKED_0 }, { .type = WMFW_HALO_YM_PACKED, .base = CS35L56_DSP1_YMEM_PACKED_0 },
{ .type = WMFW_ADSP2_XM, .base = CS35L56_DSP1_XMEM_UNPACKED24_0 }, { .type = WMFW_ADSP2_XM, .base = CS35L56_DSP1_XMEM_UNPACKED24_0 },
{ .type = WMFW_ADSP2_YM, .base = CS35L56_DSP1_YMEM_UNPACKED24_0 }, { .type = WMFW_ADSP2_YM, .base = CS35L56_DSP1_YMEM_UNPACKED24_0 },
}; };
EXPORT_SYMBOL_NS_GPL(cs35l56_dsp1_regions, SND_SOC_CS35L56_SHARED);
void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp)
{
cs_dsp->num = 1;
cs_dsp->type = WMFW_HALO;
cs_dsp->rev = 0;
cs_dsp->dev = cs35l56_base->dev;
cs_dsp->regmap = cs35l56_base->regmap;
cs_dsp->base = CS35L56_DSP1_CORE_BASE;
cs_dsp->base_sysinfo = CS35L56_DSP1_SYS_INFO_ID;
cs_dsp->mem = cs35l56_dsp1_regions;
cs_dsp->num_mems = ARRAY_SIZE(cs35l56_dsp1_regions);
cs_dsp->no_core_startstop = true;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_init_cs_dsp, SND_SOC_CS35L56_SHARED);
int cs35l56_hw_init(struct cs35l56_base *cs35l56_base)
{
int ret;
unsigned int devid, revid, otpid, secured;
/*
* If the system is not using a reset_gpio then issue a
* dummy read to force a wakeup.
*/
if (!cs35l56_base->reset_gpio)
regmap_read(cs35l56_base->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, &devid);
cs35l56_wait_control_port_ready();
/*
* The HALO_STATE register is in different locations on Ax and B0
* devices so the REVID needs to be determined before waiting for the
* firmware to boot.
*/
ret = regmap_read(cs35l56_base->regmap, CS35L56_REVID, &revid);
if (ret < 0) {
dev_err(cs35l56_base->dev, "Get Revision ID failed\n");
return ret;
}
cs35l56_base->rev = revid & (CS35L56_AREVID_MASK | CS35L56_MTLREVID_MASK);
ret = cs35l56_wait_for_firmware_boot(cs35l56_base);
if (ret)
return ret;
ret = regmap_read(cs35l56_base->regmap, CS35L56_DEVID, &devid);
if (ret < 0) {
dev_err(cs35l56_base->dev, "Get Device ID failed\n");
return ret;
}
devid &= CS35L56_DEVID_MASK;
switch (devid) {
case 0x35A56:
break;
default:
dev_err(cs35l56_base->dev, "Unknown device %x\n", devid);
return ret;
}
ret = regmap_read(cs35l56_base->regmap, CS35L56_DSP_RESTRICT_STS1, &secured);
if (ret) {
dev_err(cs35l56_base->dev, "Get Secure status failed\n");
return ret;
}
/* When any bus is restricted treat the device as secured */
if (secured & CS35L56_RESTRICTED_MASK)
cs35l56_base->secured = true;
ret = regmap_read(cs35l56_base->regmap, CS35L56_OTPID, &otpid);
if (ret < 0) {
dev_err(cs35l56_base->dev, "Get OTP ID failed\n");
return ret;
}
dev_info(cs35l56_base->dev, "Cirrus Logic CS35L56%s Rev %02X OTP%d\n",
cs35l56_base->secured ? "s" : "", cs35l56_base->rev, otpid);
/* Wake source and *_BLOCKED interrupts default to unmasked, so mask them */
regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
regmap_update_bits(cs35l56_base->regmap, CS35L56_IRQ1_MASK_1,
CS35L56_AMP_SHORT_ERR_EINT1_MASK,
0);
regmap_update_bits(cs35l56_base->regmap, CS35L56_IRQ1_MASK_8,
CS35L56_TEMP_ERR_EINT1_MASK,
0);
return 0;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_hw_init, SND_SOC_CS35L56_SHARED);
static const u32 cs35l56_bclk_valid_for_pll_freq_table[] = { static const u32 cs35l56_bclk_valid_for_pll_freq_table[] = {
[0x0C] = 128000, [0x0C] = 128000,

View File

@ -25,13 +25,13 @@ static int cs35l56_spi_probe(struct spi_device *spi)
return -ENOMEM; return -ENOMEM;
spi_set_drvdata(spi, cs35l56); spi_set_drvdata(spi, cs35l56);
cs35l56->regmap = devm_regmap_init_spi(spi, regmap_config); cs35l56->base.regmap = devm_regmap_init_spi(spi, regmap_config);
if (IS_ERR(cs35l56->regmap)) { if (IS_ERR(cs35l56->base.regmap)) {
ret = PTR_ERR(cs35l56->regmap); ret = PTR_ERR(cs35l56->base.regmap);
return dev_err_probe(&spi->dev, ret, "Failed to allocate register map\n"); return dev_err_probe(&spi->dev, ret, "Failed to allocate register map\n");
} }
cs35l56->dev = &spi->dev; cs35l56->base.dev = &spi->dev;
ret = cs35l56_common_probe(cs35l56); ret = cs35l56_common_probe(cs35l56);
if (ret != 0) if (ret != 0)
@ -39,7 +39,7 @@ static int cs35l56_spi_probe(struct spi_device *spi)
ret = cs35l56_init(cs35l56); ret = cs35l56_init(cs35l56);
if (ret == 0) if (ret == 0)
ret = cs35l56_irq_request(cs35l56, spi->irq); ret = cs35l56_irq_request(&cs35l56->base, spi->irq);
if (ret < 0) if (ret < 0)
cs35l56_remove(cs35l56); cs35l56_remove(cs35l56);

File diff suppressed because it is too large Load Diff

View File

@ -32,26 +32,17 @@ struct sdw_slave;
struct cs35l56_private { struct cs35l56_private {
struct wm_adsp dsp; /* must be first member */ struct wm_adsp dsp; /* must be first member */
struct cs35l56_base base;
struct work_struct dsp_work; struct work_struct dsp_work;
struct workqueue_struct *dsp_wq; struct workqueue_struct *dsp_wq;
struct mutex irq_lock;
struct snd_soc_component *component; struct snd_soc_component *component;
struct device *dev;
struct regmap *regmap;
struct regulator_bulk_data supplies[CS35L56_NUM_BULK_SUPPLIES]; struct regulator_bulk_data supplies[CS35L56_NUM_BULK_SUPPLIES];
int irq;
struct sdw_slave *sdw_peripheral; struct sdw_slave *sdw_peripheral;
u8 rev;
struct work_struct sdw_irq_work; struct work_struct sdw_irq_work;
bool secured;
bool sdw_irq_no_unmask; bool sdw_irq_no_unmask;
bool soft_resetting; bool soft_resetting;
bool init_done;
bool sdw_attached; bool sdw_attached;
bool fw_patched;
bool can_hibernate;
struct completion init_completion; struct completion init_completion;
struct gpio_desc *reset_gpio;
u32 rx_mask; u32 rx_mask;
u32 tx_mask; u32 tx_mask;
@ -64,8 +55,6 @@ struct cs35l56_private {
extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi; extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi;
int cs35l56_runtime_suspend(struct device *dev);
int cs35l56_runtime_resume_common(struct cs35l56_private *cs35l56);
int cs35l56_system_suspend(struct device *dev); int cs35l56_system_suspend(struct device *dev);
int cs35l56_system_suspend_late(struct device *dev); int cs35l56_system_suspend_late(struct device *dev);
int cs35l56_system_suspend_no_irq(struct device *dev); int cs35l56_system_suspend_no_irq(struct device *dev);
@ -73,7 +62,7 @@ int cs35l56_system_resume_no_irq(struct device *dev);
int cs35l56_system_resume_early(struct device *dev); int cs35l56_system_resume_early(struct device *dev);
int cs35l56_system_resume(struct device *dev); int cs35l56_system_resume(struct device *dev);
irqreturn_t cs35l56_irq(int irq, void *data); irqreturn_t cs35l56_irq(int irq, void *data);
int cs35l56_irq_request(struct cs35l56_private *cs35l56, int irq); int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq);
int cs35l56_common_probe(struct cs35l56_private *cs35l56); int cs35l56_common_probe(struct cs35l56_private *cs35l56);
int cs35l56_init(struct cs35l56_private *cs35l56); int cs35l56_init(struct cs35l56_private *cs35l56);
void cs35l56_remove(struct cs35l56_private *cs35l56); void cs35l56_remove(struct cs35l56_private *cs35l56);