mirror of
https://github.com/torvalds/linux.git
synced 2024-12-04 01:51:34 +00:00
b96ccdcf9d
Merge series from Cezary Rojewski <cezary.rojewski@intel.com>: The avs-driver continues to be utilized on more recent Intel machines. As TGL-based (cAVS 2.5) e.g.: RPL, inherit most of the functionality from previous platforms: SKL <- APL <- CNL <- ICL <- TGL rather than putting everything into a single file, the platform-specific bits are split into cnl/icl/tgl.c files instead. Makes the division clear and code easier to maintain. Layout of the patchset: First are two changes combined together address the sound-clipping problem, present when only one stream is running - specifically one CAPTURE stream. Follow up is naming-scheme adjustment for some of the existing functions what improves code incohesiveness. As existing IPC/IRQ code operates solely on cAVS 1.5 architecture, it needs no abstraction. The situation changes when newer platforms come into the picture. Thus the next two patches abstract the existing IPC/IRQ handlers so that majority of the common code can be re-used. The ICCMAX change stands out a bit - the AudioDSP firmware loading procedure differs on ICL-based platforms (and onwards) and having a separate commit makes the situation clear to the developers who are going to support the solution from LTS perspective. For that reason I decided not to merge it into the commit introducing the icl.c file.
549 lines
12 KiB
C
549 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// tas2781-lib.c -- TAS2781 Common functions for HDA and ASoC Audio drivers
|
|
//
|
|
// Copyright 2023 Texas Instruments, Inc.
|
|
//
|
|
// Author: Shenghao Ding <shenghao-ding@ti.com>
|
|
|
|
#include <linux/crc8.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/slab.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/tas2781.h>
|
|
|
|
#define TASDEVICE_CRC8_POLYNOMIAL 0x4d
|
|
|
|
static const struct regmap_range_cfg tasdevice_ranges[] = {
|
|
{
|
|
.range_min = 0,
|
|
.range_max = 256 * 128,
|
|
.selector_reg = TASDEVICE_PAGE_SELECT,
|
|
.selector_mask = 0xff,
|
|
.selector_shift = 0,
|
|
.window_start = 0,
|
|
.window_len = 128,
|
|
},
|
|
};
|
|
|
|
static const struct regmap_config tasdevice_regmap = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.cache_type = REGCACHE_NONE,
|
|
.ranges = tasdevice_ranges,
|
|
.num_ranges = ARRAY_SIZE(tasdevice_ranges),
|
|
.max_register = 256 * 128,
|
|
};
|
|
|
|
static int tasdevice_change_chn_book(struct tasdevice_priv *tas_priv,
|
|
unsigned short chn, int book)
|
|
{
|
|
struct i2c_client *client = (struct i2c_client *)tas_priv->client;
|
|
int ret = 0;
|
|
|
|
if (chn < tas_priv->ndev) {
|
|
struct tasdevice *tasdev = &tas_priv->tasdevice[chn];
|
|
struct regmap *map = tas_priv->regmap;
|
|
|
|
if (client->addr != tasdev->dev_addr) {
|
|
client->addr = tasdev->dev_addr;
|
|
/* All tas2781s share the same regmap, clear the page
|
|
* inside regmap once switching to another tas2781.
|
|
* Register 0 at any pages and any books inside tas2781
|
|
* is the same one for page-switching.
|
|
*/
|
|
ret = regmap_write(map, TASDEVICE_PAGE_SELECT, 0);
|
|
if (ret < 0) {
|
|
dev_err(tas_priv->dev, "%s, E=%d\n",
|
|
__func__, ret);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (tasdev->cur_book != book) {
|
|
ret = regmap_write(map, TASDEVICE_BOOKCTL_REG, book);
|
|
if (ret < 0) {
|
|
dev_err(tas_priv->dev, "%s, E=%d\n",
|
|
__func__, ret);
|
|
goto out;
|
|
}
|
|
tasdev->cur_book = book;
|
|
}
|
|
} else {
|
|
ret = -EINVAL;
|
|
dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__,
|
|
chn);
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int tasdevice_dev_read(struct tasdevice_priv *tas_priv,
|
|
unsigned short chn, unsigned int reg, unsigned int *val)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (chn < tas_priv->ndev) {
|
|
struct regmap *map = tas_priv->regmap;
|
|
|
|
ret = tasdevice_change_chn_book(tas_priv, chn,
|
|
TASDEVICE_BOOK_ID(reg));
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = regmap_read(map, TASDEVICE_PGRG(reg), val);
|
|
if (ret < 0)
|
|
dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
|
|
} else {
|
|
ret = -EINVAL;
|
|
dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__,
|
|
chn);
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_dev_read);
|
|
|
|
int tasdevice_dev_write(struct tasdevice_priv *tas_priv,
|
|
unsigned short chn, unsigned int reg, unsigned int value)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (chn < tas_priv->ndev) {
|
|
struct regmap *map = tas_priv->regmap;
|
|
|
|
ret = tasdevice_change_chn_book(tas_priv, chn,
|
|
TASDEVICE_BOOK_ID(reg));
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = regmap_write(map, TASDEVICE_PGRG(reg),
|
|
value);
|
|
if (ret < 0)
|
|
dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
|
|
} else {
|
|
ret = -EINVAL;
|
|
dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__,
|
|
chn);
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_dev_write);
|
|
|
|
int tasdevice_dev_bulk_write(
|
|
struct tasdevice_priv *tas_priv, unsigned short chn,
|
|
unsigned int reg, unsigned char *data,
|
|
unsigned int len)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (chn < tas_priv->ndev) {
|
|
struct regmap *map = tas_priv->regmap;
|
|
|
|
ret = tasdevice_change_chn_book(tas_priv, chn,
|
|
TASDEVICE_BOOK_ID(reg));
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = regmap_bulk_write(map, TASDEVICE_PGRG(reg),
|
|
data, len);
|
|
if (ret < 0)
|
|
dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
|
|
} else {
|
|
ret = -EINVAL;
|
|
dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__,
|
|
chn);
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_dev_bulk_write);
|
|
|
|
int tasdevice_dev_bulk_read(struct tasdevice_priv *tas_priv,
|
|
unsigned short chn, unsigned int reg, unsigned char *data,
|
|
unsigned int len)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (chn < tas_priv->ndev) {
|
|
struct regmap *map = tas_priv->regmap;
|
|
|
|
ret = tasdevice_change_chn_book(tas_priv, chn,
|
|
TASDEVICE_BOOK_ID(reg));
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = regmap_bulk_read(map, TASDEVICE_PGRG(reg), data, len);
|
|
if (ret < 0)
|
|
dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
|
|
} else
|
|
dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__,
|
|
chn);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_dev_bulk_read);
|
|
|
|
int tasdevice_dev_update_bits(
|
|
struct tasdevice_priv *tas_priv, unsigned short chn,
|
|
unsigned int reg, unsigned int mask, unsigned int value)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (chn < tas_priv->ndev) {
|
|
struct regmap *map = tas_priv->regmap;
|
|
|
|
ret = tasdevice_change_chn_book(tas_priv, chn,
|
|
TASDEVICE_BOOK_ID(reg));
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = regmap_update_bits(map, TASDEVICE_PGRG(reg),
|
|
mask, value);
|
|
if (ret < 0)
|
|
dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
|
|
} else {
|
|
dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__,
|
|
chn);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_dev_update_bits);
|
|
|
|
struct tasdevice_priv *tasdevice_kzalloc(struct i2c_client *i2c)
|
|
{
|
|
struct tasdevice_priv *tas_priv;
|
|
|
|
tas_priv = devm_kzalloc(&i2c->dev, sizeof(*tas_priv), GFP_KERNEL);
|
|
if (!tas_priv)
|
|
return NULL;
|
|
tas_priv->dev = &i2c->dev;
|
|
tas_priv->client = (void *)i2c;
|
|
|
|
return tas_priv;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_kzalloc);
|
|
|
|
void tas2781_reset(struct tasdevice_priv *tas_dev)
|
|
{
|
|
int ret, i;
|
|
|
|
if (tas_dev->reset) {
|
|
gpiod_set_value_cansleep(tas_dev->reset, 0);
|
|
usleep_range(500, 1000);
|
|
gpiod_set_value_cansleep(tas_dev->reset, 1);
|
|
} else {
|
|
for (i = 0; i < tas_dev->ndev; i++) {
|
|
ret = tasdevice_dev_write(tas_dev, i,
|
|
TAS2781_REG_SWRESET,
|
|
TAS2781_REG_SWRESET_RESET);
|
|
if (ret < 0)
|
|
dev_err(tas_dev->dev,
|
|
"dev %d swreset fail, %d\n",
|
|
i, ret);
|
|
}
|
|
}
|
|
usleep_range(1000, 1050);
|
|
}
|
|
EXPORT_SYMBOL_GPL(tas2781_reset);
|
|
|
|
int tascodec_init(struct tasdevice_priv *tas_priv, void *codec,
|
|
struct module *module,
|
|
void (*cont)(const struct firmware *fw, void *context))
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Codec Lock Hold to ensure that codec_probe and firmware parsing and
|
|
* loading do not simultaneously execute.
|
|
*/
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
scnprintf(tas_priv->rca_binaryname, 64, "%sRCA%d.bin",
|
|
tas_priv->dev_name, tas_priv->ndev);
|
|
crc8_populate_msb(tas_priv->crc8_lkp_tbl, TASDEVICE_CRC8_POLYNOMIAL);
|
|
tas_priv->codec = codec;
|
|
ret = request_firmware_nowait(module, FW_ACTION_UEVENT,
|
|
tas_priv->rca_binaryname, tas_priv->dev, GFP_KERNEL, tas_priv,
|
|
cont);
|
|
if (ret)
|
|
dev_err(tas_priv->dev, "request_firmware_nowait err:0x%08x\n",
|
|
ret);
|
|
|
|
/* Codec Lock Release*/
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tascodec_init);
|
|
|
|
int tasdevice_init(struct tasdevice_priv *tas_priv)
|
|
{
|
|
int ret = 0;
|
|
int i;
|
|
|
|
tas_priv->regmap = devm_regmap_init_i2c(tas_priv->client,
|
|
&tasdevice_regmap);
|
|
if (IS_ERR(tas_priv->regmap)) {
|
|
ret = PTR_ERR(tas_priv->regmap);
|
|
dev_err(tas_priv->dev, "Failed to allocate register map: %d\n",
|
|
ret);
|
|
goto out;
|
|
}
|
|
|
|
tas_priv->cur_prog = -1;
|
|
tas_priv->cur_conf = -1;
|
|
|
|
for (i = 0; i < tas_priv->ndev; i++) {
|
|
tas_priv->tasdevice[i].cur_book = -1;
|
|
tas_priv->tasdevice[i].cur_prog = -1;
|
|
tas_priv->tasdevice[i].cur_conf = -1;
|
|
}
|
|
|
|
mutex_init(&tas_priv->codec_lock);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_init);
|
|
|
|
static void tasdev_dsp_prog_blk_remove(struct tasdevice_prog *prog)
|
|
{
|
|
struct tasdevice_data *tas_dt;
|
|
struct tasdev_blk *blk;
|
|
unsigned int i;
|
|
|
|
if (!prog)
|
|
return;
|
|
|
|
tas_dt = &(prog->dev_data);
|
|
|
|
if (!tas_dt->dev_blks)
|
|
return;
|
|
|
|
for (i = 0; i < tas_dt->nr_blk; i++) {
|
|
blk = &(tas_dt->dev_blks[i]);
|
|
kfree(blk->data);
|
|
}
|
|
kfree(tas_dt->dev_blks);
|
|
}
|
|
|
|
static void tasdev_dsp_prog_remove(struct tasdevice_prog *prog,
|
|
unsigned short nr)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nr; i++)
|
|
tasdev_dsp_prog_blk_remove(&prog[i]);
|
|
kfree(prog);
|
|
}
|
|
|
|
static void tasdev_dsp_cfg_blk_remove(struct tasdevice_config *cfg)
|
|
{
|
|
struct tasdevice_data *tas_dt;
|
|
struct tasdev_blk *blk;
|
|
unsigned int i;
|
|
|
|
if (cfg) {
|
|
tas_dt = &(cfg->dev_data);
|
|
|
|
if (!tas_dt->dev_blks)
|
|
return;
|
|
|
|
for (i = 0; i < tas_dt->nr_blk; i++) {
|
|
blk = &(tas_dt->dev_blks[i]);
|
|
kfree(blk->data);
|
|
}
|
|
kfree(tas_dt->dev_blks);
|
|
}
|
|
}
|
|
|
|
static void tasdev_dsp_cfg_remove(struct tasdevice_config *config,
|
|
unsigned short nr)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nr; i++)
|
|
tasdev_dsp_cfg_blk_remove(&config[i]);
|
|
kfree(config);
|
|
}
|
|
|
|
void tasdevice_dsp_remove(void *context)
|
|
{
|
|
struct tasdevice_priv *tas_dev = (struct tasdevice_priv *) context;
|
|
struct tasdevice_fw *tas_fmw = tas_dev->fmw;
|
|
|
|
if (!tas_dev->fmw)
|
|
return;
|
|
|
|
if (tas_fmw->programs)
|
|
tasdev_dsp_prog_remove(tas_fmw->programs,
|
|
tas_fmw->nr_programs);
|
|
if (tas_fmw->configs)
|
|
tasdev_dsp_cfg_remove(tas_fmw->configs,
|
|
tas_fmw->nr_configurations);
|
|
kfree(tas_fmw);
|
|
tas_dev->fmw = NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_dsp_remove);
|
|
|
|
void tasdevice_remove(struct tasdevice_priv *tas_priv)
|
|
{
|
|
if (gpio_is_valid(tas_priv->irq_info.irq_gpio))
|
|
gpio_free(tas_priv->irq_info.irq_gpio);
|
|
mutex_destroy(&tas_priv->codec_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_remove);
|
|
|
|
int tasdevice_save_calibration(struct tasdevice_priv *tas_priv)
|
|
{
|
|
if (tas_priv->save_calibration)
|
|
return tas_priv->save_calibration(tas_priv);
|
|
return -EINVAL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_save_calibration);
|
|
|
|
void tasdevice_apply_calibration(struct tasdevice_priv *tas_priv)
|
|
{
|
|
if (tas_priv->apply_calibration && tas_priv->cali_data.total_sz)
|
|
tas_priv->apply_calibration(tas_priv);
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_apply_calibration);
|
|
|
|
static int tasdevice_clamp(int val, int max, unsigned int invert)
|
|
{
|
|
if (val > max)
|
|
val = max;
|
|
if (invert)
|
|
val = max - val;
|
|
if (val < 0)
|
|
val = 0;
|
|
return val;
|
|
}
|
|
|
|
int tasdevice_amp_putvol(struct tasdevice_priv *tas_priv,
|
|
struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc)
|
|
{
|
|
unsigned int invert = mc->invert;
|
|
unsigned char mask;
|
|
int max = mc->max;
|
|
int err_cnt = 0;
|
|
int val, i, ret;
|
|
|
|
mask = (1 << fls(max)) - 1;
|
|
mask <<= mc->shift;
|
|
val = tasdevice_clamp(ucontrol->value.integer.value[0], max, invert);
|
|
for (i = 0; i < tas_priv->ndev; i++) {
|
|
ret = tasdevice_dev_update_bits(tas_priv, i,
|
|
mc->reg, mask, (unsigned int)(val << mc->shift));
|
|
if (!ret)
|
|
continue;
|
|
err_cnt++;
|
|
dev_err(tas_priv->dev, "set AMP vol error in dev %d\n", i);
|
|
}
|
|
|
|
/* All the devices set error, return 0 */
|
|
return (err_cnt == tas_priv->ndev) ? 0 : 1;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_amp_putvol);
|
|
|
|
int tasdevice_amp_getvol(struct tasdevice_priv *tas_priv,
|
|
struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc)
|
|
{
|
|
unsigned int invert = mc->invert;
|
|
unsigned char mask = 0;
|
|
int max = mc->max;
|
|
int ret = 0;
|
|
int val;
|
|
|
|
/* Read the primary device */
|
|
ret = tasdevice_dev_read(tas_priv, 0, mc->reg, &val);
|
|
if (ret) {
|
|
dev_err(tas_priv->dev, "%s, get AMP vol error\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
mask = (1 << fls(max)) - 1;
|
|
mask <<= mc->shift;
|
|
val = (val & mask) >> mc->shift;
|
|
val = tasdevice_clamp(val, max, invert);
|
|
ucontrol->value.integer.value[0] = val;
|
|
|
|
out:
|
|
return ret;
|
|
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_amp_getvol);
|
|
|
|
int tasdevice_digital_putvol(struct tasdevice_priv *tas_priv,
|
|
struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc)
|
|
{
|
|
unsigned int invert = mc->invert;
|
|
int max = mc->max;
|
|
int err_cnt = 0;
|
|
int ret;
|
|
int val, i;
|
|
|
|
val = tasdevice_clamp(ucontrol->value.integer.value[0], max, invert);
|
|
|
|
for (i = 0; i < tas_priv->ndev; i++) {
|
|
ret = tasdevice_dev_write(tas_priv, i, mc->reg,
|
|
(unsigned int)val);
|
|
if (!ret)
|
|
continue;
|
|
err_cnt++;
|
|
dev_err(tas_priv->dev,
|
|
"set digital vol err in dev %d\n", i);
|
|
}
|
|
|
|
/* All the devices set error, return 0 */
|
|
return (err_cnt == tas_priv->ndev) ? 0 : 1;
|
|
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_digital_putvol);
|
|
|
|
int tasdevice_digital_getvol(struct tasdevice_priv *tas_priv,
|
|
struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc)
|
|
{
|
|
unsigned int invert = mc->invert;
|
|
int max = mc->max;
|
|
int ret, val;
|
|
|
|
/* Read the primary device as the whole */
|
|
ret = tasdevice_dev_read(tas_priv, 0, mc->reg, &val);
|
|
if (ret) {
|
|
dev_err(tas_priv->dev, "%s, get digital vol error\n",
|
|
__func__);
|
|
goto out;
|
|
}
|
|
|
|
val = tasdevice_clamp(val, max, invert);
|
|
ucontrol->value.integer.value[0] = val;
|
|
|
|
out:
|
|
return ret;
|
|
|
|
}
|
|
EXPORT_SYMBOL_GPL(tasdevice_digital_getvol);
|
|
|
|
MODULE_DESCRIPTION("TAS2781 common library");
|
|
MODULE_AUTHOR("Shenghao Ding, TI, <shenghao-ding@ti.com>");
|
|
MODULE_LICENSE("GPL");
|