1587 lines
37 KiB
C
1587 lines
37 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// fs210x.c -- Driver for the FS2104/5S Audio Amplifier
|
|
//
|
|
// Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd.
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/workqueue.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/tlv.h>
|
|
|
|
#include "fs210x.h"
|
|
#include "fs-amp-lib.h"
|
|
|
|
#define FS210X_DEFAULT_FWM_NAME "fs210x_fwm.bin"
|
|
#define FS210X_DEFAULT_DAI_NAME "fs210x-aif"
|
|
#define FS2105S_DEVICE_ID 0x20 /* FS2105S */
|
|
#define FS210X_DEVICE_ID 0x45 /* FS2104 */
|
|
#define FS210X_REG_MAX 0xF8
|
|
#define FS210X_INIT_SCENE 0
|
|
#define FS210X_DEFAULT_SCENE 1
|
|
#define FS210X_START_DELAY_MS 5
|
|
#define FS210X_FAULT_CHECK_INTERVAL_MS 2000
|
|
#define FS2105S_RATES (SNDRV_PCM_RATE_32000 | \
|
|
SNDRV_PCM_RATE_44100 | \
|
|
SNDRV_PCM_RATE_48000 | \
|
|
SNDRV_PCM_RATE_88200 | \
|
|
SNDRV_PCM_RATE_96000)
|
|
#define FS210X_RATES (SNDRV_PCM_RATE_16000 | FS2105S_RATES)
|
|
#define FS210X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
|
|
SNDRV_PCM_FMTBIT_S24_LE | \
|
|
SNDRV_PCM_FMTBIT_S24_3LE | \
|
|
SNDRV_PCM_FMTBIT_S32_LE)
|
|
#define FS210X_NUM_SUPPLIES ARRAY_SIZE(fs210x_supply_names)
|
|
|
|
static const char *const fs210x_supply_names[] = {
|
|
"pvdd",
|
|
"dvdd",
|
|
};
|
|
|
|
struct fs210x_platform_data {
|
|
const char *fwm_name;
|
|
};
|
|
|
|
struct fs210x_priv {
|
|
struct i2c_client *i2c;
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct fs210x_platform_data pdata;
|
|
struct regulator_bulk_data supplies[FS210X_NUM_SUPPLIES];
|
|
struct gpio_desc *gpio_sdz;
|
|
struct delayed_work start_work;
|
|
struct delayed_work fault_check_work;
|
|
struct fs_amp_lib amp_lib;
|
|
const struct fs_amp_scene *cur_scene;
|
|
struct clk *clk_bclk;
|
|
/*
|
|
* @lock: Mutex ensuring exclusive access for critical device operations
|
|
*
|
|
* This lock serializes access between the following actions:
|
|
* - Device initialization procedures(probe)
|
|
* - Enable/disable device(DAPM event)
|
|
* - Suspend/resume device(PM)
|
|
* - Runtime scene switching(control)
|
|
* - Scheduling/execution of delayed works items(delayed works)
|
|
*/
|
|
struct mutex lock;
|
|
unsigned int check_interval_ms;
|
|
unsigned int bclk;
|
|
unsigned int srate;
|
|
int scene_id;
|
|
u16 devid;
|
|
bool is_inited;
|
|
bool is_suspended;
|
|
bool is_bclk_on;
|
|
bool is_playing;
|
|
};
|
|
|
|
static const unsigned int fs2105s_rates[] = {
|
|
32000, 44100, 48000, 88200, 96000
|
|
};
|
|
|
|
static const struct snd_pcm_hw_constraint_list fs2105s_constraints = {
|
|
.count = ARRAY_SIZE(fs2105s_rates),
|
|
.list = fs2105s_rates,
|
|
};
|
|
|
|
static const unsigned int fs210x_rates[] = {
|
|
16000, 32000, 44100, 48000, 88200, 96000
|
|
};
|
|
|
|
static const struct snd_pcm_hw_constraint_list fs210x_constraints = {
|
|
.count = ARRAY_SIZE(fs210x_rates),
|
|
.list = fs210x_rates,
|
|
};
|
|
|
|
static const struct fs_pll_div fs210x_pll_div[] = {
|
|
/* bclk, pll1, pll2, pll3 */
|
|
{ 512000, 0x006C, 0x0120, 0x0001 },
|
|
{ 768000, 0x016C, 0x00C0, 0x0001 },
|
|
{ 1024000, 0x016C, 0x0090, 0x0001 },
|
|
{ 1536000, 0x016C, 0x0060, 0x0001 },
|
|
{ 2048000, 0x016C, 0x0090, 0x0002 },
|
|
{ 2304000, 0x016C, 0x0080, 0x0002 },
|
|
{ 3072000, 0x016C, 0x0090, 0x0003 },
|
|
{ 4096000, 0x016C, 0x0090, 0x0004 },
|
|
{ 4608000, 0x016C, 0x0080, 0x0004 },
|
|
{ 6144000, 0x016C, 0x0090, 0x0006 },
|
|
{ 8192000, 0x016C, 0x0090, 0x0008 },
|
|
{ 9216000, 0x016C, 0x0090, 0x0009 },
|
|
{ 12288000, 0x016C, 0x0090, 0x000C },
|
|
{ 16384000, 0x016C, 0x0090, 0x0010 },
|
|
{ 18432000, 0x016C, 0x0090, 0x0012 },
|
|
{ 24576000, 0x016C, 0x0090, 0x0018 },
|
|
{ 1411200, 0x016C, 0x0060, 0x0001 },
|
|
{ 2116800, 0x016C, 0x0080, 0x0002 },
|
|
{ 2822400, 0x016C, 0x0090, 0x0003 },
|
|
{ 4233600, 0x016C, 0x0080, 0x0004 },
|
|
{ 5644800, 0x016C, 0x0090, 0x0006 },
|
|
{ 8467200, 0x016C, 0x0090, 0x0009 },
|
|
{ 11289600, 0x016C, 0x0090, 0x000C },
|
|
{ 16934400, 0x016C, 0x0090, 0x0012 },
|
|
{ 22579200, 0x016C, 0x0090, 0x0018 },
|
|
{ 2000000, 0x017C, 0x0093, 0x0002 },
|
|
};
|
|
|
|
static int fs210x_bclk_set(struct fs210x_priv *fs210x, bool on)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!fs210x || !fs210x->dev)
|
|
return -EINVAL;
|
|
|
|
if ((fs210x->is_bclk_on ^ on) == 0)
|
|
return 0;
|
|
|
|
if (on) {
|
|
clk_set_rate(fs210x->clk_bclk, fs210x->bclk);
|
|
ret = clk_prepare_enable(fs210x->clk_bclk);
|
|
fs210x->is_bclk_on = true;
|
|
fsleep(2000); /* >= 2ms */
|
|
} else {
|
|
clk_disable_unprepare(fs210x->clk_bclk);
|
|
fs210x->is_bclk_on = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_reg_write(struct fs210x_priv *fs210x,
|
|
u8 reg, u16 val)
|
|
{
|
|
int ret;
|
|
|
|
ret = regmap_write(fs210x->regmap, reg, val);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to write %02Xh: %d\n", reg, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_reg_read(struct fs210x_priv *fs210x,
|
|
u8 reg, u16 *pval)
|
|
{
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
ret = regmap_read(fs210x->regmap, reg, &val);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to read %02Xh: %d\n", reg, ret);
|
|
return ret;
|
|
}
|
|
|
|
*pval = (u16)val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_reg_update_bits(struct fs210x_priv *fs210x,
|
|
u8 reg, u16 mask, u16 val)
|
|
{
|
|
int ret;
|
|
|
|
ret = regmap_update_bits(fs210x->regmap, reg, mask, val);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to update %02Xh: %d\n", reg, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_reg_bulk_write(struct fs210x_priv *fs210x,
|
|
u8 reg, const void *val, u32 size)
|
|
{
|
|
int ret;
|
|
|
|
ret = regmap_bulk_write(fs210x->regmap, reg, val, size / 2);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to bulk write %02Xh: %d\n",
|
|
reg, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int fs210x_write_reg_val(struct fs210x_priv *fs210x,
|
|
const struct fs_reg_val *regv)
|
|
{
|
|
return fs210x_reg_write(fs210x, regv->reg, regv->val);
|
|
}
|
|
|
|
static inline int fs210x_write_reg_bits(struct fs210x_priv *fs210x,
|
|
const struct fs_reg_bits *regu)
|
|
{
|
|
return fs210x_reg_update_bits(fs210x,
|
|
regu->reg,
|
|
regu->mask,
|
|
regu->val);
|
|
}
|
|
|
|
static inline int fs210x_set_cmd_pkg(struct fs210x_priv *fs210x,
|
|
const struct fs_cmd_pkg *pkg,
|
|
unsigned int *offset)
|
|
{
|
|
int delay_us;
|
|
|
|
if (pkg->cmd >= 0x00 && pkg->cmd <= FS210X_REG_MAX) {
|
|
*offset = sizeof(pkg->regv);
|
|
return fs210x_write_reg_val(fs210x, &pkg->regv);
|
|
} else if (pkg->cmd == FS_CMD_UPDATE) {
|
|
*offset = sizeof(pkg->regb);
|
|
return fs210x_write_reg_bits(fs210x, &pkg->regb);
|
|
} else if (pkg->cmd == FS_CMD_DELAY) {
|
|
if (pkg->regv.val > FS_CMD_DELAY_MS_MAX)
|
|
return -EOPNOTSUPP;
|
|
delay_us = pkg->regv.val * 1000; /* ms -> us */
|
|
fsleep(delay_us);
|
|
*offset = sizeof(pkg->regv);
|
|
return 0;
|
|
}
|
|
|
|
dev_err(fs210x->dev, "Invalid pkg cmd: %d\n", pkg->cmd);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static int fs210x_reg_write_table(struct fs210x_priv *fs210x,
|
|
const struct fs_reg_table *reg)
|
|
{
|
|
const struct fs_cmd_pkg *pkg;
|
|
unsigned int index, offset;
|
|
int ret;
|
|
|
|
if (!fs210x || !fs210x->dev)
|
|
return -EINVAL;
|
|
|
|
if (!reg || reg->size == 0)
|
|
return -EFAULT;
|
|
|
|
for (index = 0; index < reg->size; index += offset) {
|
|
pkg = (struct fs_cmd_pkg *)(reg->buf + index);
|
|
ret = fs210x_set_cmd_pkg(fs210x, pkg, &offset);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to set cmd pkg: %02X-%d\n",
|
|
pkg->cmd, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (index != reg->size) {
|
|
dev_err(fs210x->dev, "Invalid reg table size: %d-%d\n",
|
|
index, reg->size);
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_dev_play(struct fs210x_priv *fs210x)
|
|
{
|
|
int ret;
|
|
|
|
if (!fs210x->is_inited)
|
|
return -EFAULT;
|
|
|
|
if (fs210x->is_playing)
|
|
return 0;
|
|
|
|
ret = fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL,
|
|
FS210X_11H_DPS_PLAY);
|
|
if (!ret)
|
|
fs210x->is_playing = true;
|
|
|
|
fsleep(10000); /* >= 10ms */
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_dev_stop(struct fs210x_priv *fs210x)
|
|
{
|
|
int ret;
|
|
|
|
if (!fs210x->is_inited)
|
|
return -EFAULT;
|
|
|
|
if (!fs210x->is_playing)
|
|
return 0;
|
|
|
|
ret = fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL,
|
|
FS210X_11H_DPS_PWDN);
|
|
fs210x->is_playing = false;
|
|
|
|
fsleep(30000); /* >= 30ms */
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_set_reg_table(struct fs210x_priv *fs210x,
|
|
const struct fs_amp_scene *scene)
|
|
{
|
|
const struct fs_amp_scene *cur_scene;
|
|
const struct fs_reg_table *reg;
|
|
|
|
if (!fs210x || !fs210x->dev || !scene)
|
|
return -EINVAL;
|
|
|
|
cur_scene = fs210x->cur_scene;
|
|
if (!scene->reg || cur_scene == scene) {
|
|
dev_dbg(fs210x->dev, "Skip writing reg table\n");
|
|
return 0;
|
|
}
|
|
|
|
reg = scene->reg;
|
|
dev_dbg(fs210x->dev, "reg table size: %d\n", reg->size);
|
|
|
|
return fs210x_reg_write_table(fs210x, reg);
|
|
}
|
|
|
|
static int fs210x_set_woofer_table(struct fs210x_priv *fs210x)
|
|
{
|
|
const struct fs_file_table *woofer;
|
|
const struct fs_fwm_table *table;
|
|
int ret;
|
|
|
|
if (!fs210x || !fs210x->dev)
|
|
return -EINVAL;
|
|
|
|
/* NOTE: fs2105s has woofer ram only */
|
|
if (fs210x->devid != FS2105S_DEVICE_ID)
|
|
return 0;
|
|
|
|
table = fs210x->amp_lib.table[FS_INDEX_WOOFER];
|
|
if (!table) {
|
|
dev_dbg(fs210x->dev, "Skip writing woofer table\n");
|
|
return 0;
|
|
}
|
|
|
|
woofer = (struct fs_file_table *)table->buf;
|
|
dev_dbg(fs210x->dev, "woofer table size: %d\n", woofer->size);
|
|
/* Unit of woofer data is u32(4 bytes) */
|
|
if (woofer->size == 0 || (woofer->size & 0x3)) {
|
|
dev_err(fs210x->dev, "Invalid woofer size: %d\n",
|
|
woofer->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = fs210x_reg_write(fs210x, FS210X_46H_DACEQA,
|
|
FS2105S_46H_CAM_BURST_W);
|
|
ret |= fs210x_reg_bulk_write(fs210x, FS210X_42H_DACEQWL,
|
|
woofer->buf, woofer->size);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_set_effect_table(struct fs210x_priv *fs210x,
|
|
const struct fs_amp_scene *scene)
|
|
{
|
|
const struct fs_amp_scene *cur_scene;
|
|
const struct fs_file_table *effect;
|
|
int half_size;
|
|
int ret;
|
|
|
|
if (!fs210x || !fs210x->dev || !scene)
|
|
return -EINVAL;
|
|
|
|
cur_scene = fs210x->cur_scene;
|
|
if (!scene->effect || cur_scene == scene) {
|
|
dev_dbg(fs210x->dev, "Skip writing effect table\n");
|
|
return 0;
|
|
}
|
|
|
|
effect = scene->effect;
|
|
dev_dbg(fs210x->dev, "effect table size: %d\n", effect->size);
|
|
|
|
/* Unit of effect data is u32(4 bytes), 2 channels */
|
|
if (effect->size == 0 || (effect->size & 0x7)) {
|
|
dev_err(fs210x->dev, "Invalid effect size: %d\n",
|
|
effect->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
half_size = effect->size / 2;
|
|
|
|
/* Left channel */
|
|
ret = fs210x_reg_write(fs210x, FS210X_46H_DACEQA,
|
|
FS210X_46H_CAM_BURST_L);
|
|
ret |= fs210x_reg_bulk_write(fs210x, FS210X_42H_DACEQWL,
|
|
effect->buf, half_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Right channel */
|
|
ret = fs210x_reg_write(fs210x, FS210X_46H_DACEQA,
|
|
FS210X_46H_CAM_BURST_R);
|
|
ret |= fs210x_reg_bulk_write(fs210x, FS210X_42H_DACEQWL,
|
|
effect->buf + half_size, half_size);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_access_dsp_ram(struct fs210x_priv *fs210x, bool enable)
|
|
{
|
|
int ret;
|
|
|
|
if (!fs210x || !fs210x->dev)
|
|
return -EINVAL;
|
|
|
|
if (enable) {
|
|
ret = fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL,
|
|
FS210X_11H_DPS_HIZ);
|
|
ret |= fs210x_reg_write(fs210x, FS210X_0BH_ACCKEY,
|
|
FS210X_0BH_ACCKEY_ON);
|
|
} else {
|
|
ret = fs210x_reg_write(fs210x, FS210X_0BH_ACCKEY,
|
|
FS210X_0BH_ACCKEY_OFF);
|
|
ret |= fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL,
|
|
FS210X_11H_DPS_PWDN);
|
|
}
|
|
|
|
fsleep(10000); /* >= 10ms */
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_write_dsp_effect(struct fs210x_priv *fs210x,
|
|
const struct fs_amp_scene *scene,
|
|
int scene_id)
|
|
{
|
|
int ret;
|
|
|
|
if (!fs210x || !scene)
|
|
return -EINVAL;
|
|
|
|
ret = fs210x_access_dsp_ram(fs210x, true);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to access dsp: %d\n", ret);
|
|
goto tag_exit;
|
|
}
|
|
|
|
ret = fs210x_set_effect_table(fs210x, scene);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to set effect: %d\n", ret);
|
|
goto tag_exit;
|
|
}
|
|
|
|
if (scene_id == FS210X_INIT_SCENE)
|
|
ret = fs210x_set_woofer_table(fs210x);
|
|
|
|
tag_exit:
|
|
fs210x_reg_write(fs210x, FS210X_46H_DACEQA,
|
|
FS210X_46H_CAM_CLEAR);
|
|
fs210x_access_dsp_ram(fs210x, false);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_check_scene(struct fs210x_priv *fs210x,
|
|
int scene_id, bool *skip_set)
|
|
{
|
|
struct fs_amp_lib *amp_lib;
|
|
|
|
if (!fs210x || !skip_set)
|
|
return -EINVAL;
|
|
|
|
amp_lib = &fs210x->amp_lib;
|
|
if (amp_lib->scene_count == 0 || !amp_lib->scene) {
|
|
dev_err(fs210x->dev, "There's no scene data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (scene_id < 0 || scene_id >= amp_lib->scene_count) {
|
|
dev_err(fs210x->dev, "Invalid scene_id: %d\n", scene_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (fs210x->scene_id == scene_id) {
|
|
dev_dbg(fs210x->dev, "Skip to set same scene\n");
|
|
return 0;
|
|
}
|
|
|
|
*skip_set = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_set_scene(struct fs210x_priv *fs210x, int scene_id)
|
|
{
|
|
const struct fs_amp_scene *scene;
|
|
bool skip_set = true;
|
|
bool is_playing;
|
|
int ret;
|
|
|
|
if (!fs210x || !fs210x->dev)
|
|
return -EINVAL;
|
|
|
|
ret = fs210x_check_scene(fs210x, scene_id, &skip_set);
|
|
if (ret || skip_set)
|
|
return ret;
|
|
|
|
scene = fs210x->amp_lib.scene + scene_id;
|
|
dev_info(fs210x->dev, "Switch scene.%d: %s\n",
|
|
scene_id, scene->name);
|
|
|
|
is_playing = fs210x->is_playing;
|
|
if (is_playing)
|
|
fs210x_dev_stop(fs210x);
|
|
|
|
ret = fs210x_set_reg_table(fs210x, scene);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to set reg: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = fs210x_write_dsp_effect(fs210x, scene, scene_id);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to write ram: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
fs210x->cur_scene = scene;
|
|
fs210x->scene_id = scene_id;
|
|
|
|
if (is_playing)
|
|
fs210x_dev_play(fs210x);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_init_chip(struct fs210x_priv *fs210x)
|
|
{
|
|
int scene_id;
|
|
int ret;
|
|
|
|
regcache_cache_bypass(fs210x->regmap, true);
|
|
|
|
if (!fs210x->gpio_sdz) {
|
|
/* Gpio is not found, i2c reset */
|
|
ret = fs210x_reg_write(fs210x, FS210X_10H_PWRCTRL,
|
|
FS210X_10H_I2C_RESET);
|
|
if (ret)
|
|
goto tag_power_down;
|
|
} else {
|
|
/* gpio reset, deactivate */
|
|
gpiod_set_value_cansleep(fs210x->gpio_sdz, 0);
|
|
}
|
|
|
|
fsleep(10000); /* >= 10ms */
|
|
|
|
/* Backup scene id */
|
|
scene_id = fs210x->scene_id;
|
|
fs210x->scene_id = -1;
|
|
|
|
/* Init registers/RAM by init scene */
|
|
ret = fs210x_set_scene(fs210x, FS210X_INIT_SCENE);
|
|
if (ret)
|
|
goto tag_power_down;
|
|
|
|
/*
|
|
* If the firmware has effect scene(s),
|
|
* we load effect scene by default scene or scene_id
|
|
*/
|
|
if (fs210x->amp_lib.scene_count > 1) {
|
|
if (scene_id < FS210X_DEFAULT_SCENE)
|
|
scene_id = FS210X_DEFAULT_SCENE;
|
|
ret = fs210x_set_scene(fs210x, scene_id);
|
|
if (ret)
|
|
goto tag_power_down;
|
|
}
|
|
|
|
tag_power_down:
|
|
/* Power down the device */
|
|
ret |= fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL,
|
|
FS210X_11H_DPS_PWDN);
|
|
fsleep(10000); /* >= 10ms */
|
|
|
|
regcache_cache_bypass(fs210x->regmap, false);
|
|
if (!ret) {
|
|
regcache_mark_dirty(fs210x->regmap);
|
|
regcache_sync(fs210x->regmap);
|
|
fs210x->is_inited = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_set_i2s_params(struct fs210x_priv *fs210x)
|
|
{
|
|
const struct fs_i2s_srate params[] = {
|
|
{ 16000, 0x3 },
|
|
{ 32000, 0x7 },
|
|
{ 44100, 0x8 },
|
|
{ 48000, 0x9 },
|
|
{ 88200, 0xA },
|
|
{ 96000, 0xB },
|
|
};
|
|
u16 val;
|
|
int i, ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(params); i++) {
|
|
if (params[i].srate != fs210x->srate)
|
|
continue;
|
|
val = params[i].i2ssr << FS210X_17H_I2SSR_SHIFT;
|
|
ret = fs210x_reg_update_bits(fs210x,
|
|
FS210X_17H_I2SCTRL,
|
|
FS210X_17H_I2SSR_MASK,
|
|
val);
|
|
return ret;
|
|
}
|
|
|
|
dev_err(fs210x->dev, "Invalid sample rate: %d\n", fs210x->srate);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int fs210x_get_pll_div(struct fs210x_priv *fs210x,
|
|
const struct fs_pll_div **pll_div)
|
|
{
|
|
int i;
|
|
|
|
if (!fs210x || !pll_div)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(fs210x_pll_div); i++) {
|
|
if (fs210x_pll_div[i].bclk != fs210x->bclk)
|
|
continue;
|
|
*pll_div = fs210x_pll_div + i;
|
|
return 0;
|
|
}
|
|
|
|
dev_err(fs210x->dev, "No PLL table for bclk: %d\n", fs210x->bclk);
|
|
|
|
return -EFAULT;
|
|
}
|
|
|
|
static int fs210x_set_hw_params(struct fs210x_priv *fs210x)
|
|
{
|
|
const struct fs_pll_div *pll_div;
|
|
int ret;
|
|
|
|
ret = fs210x_set_i2s_params(fs210x);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to set i2s params: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Set pll params */
|
|
ret = fs210x_get_pll_div(fs210x, &pll_div);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = fs210x_reg_write(fs210x, FS210X_A1H_PLLCTRL1, pll_div->pll1);
|
|
ret |= fs210x_reg_write(fs210x, FS210X_A2H_PLLCTRL2, pll_div->pll2);
|
|
ret |= fs210x_reg_write(fs210x, FS210X_A3H_PLLCTRL3, pll_div->pll3);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_dai_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
const struct snd_pcm_hw_constraint_list *list;
|
|
struct fs210x_priv *fs210x;
|
|
int ret;
|
|
|
|
fs210x = snd_soc_component_get_drvdata(dai->component);
|
|
if (!fs210x) {
|
|
pr_err("dai_startup: fs210x is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!substream->runtime)
|
|
return 0;
|
|
|
|
ret = snd_pcm_hw_constraint_mask64(substream->runtime,
|
|
SNDRV_PCM_HW_PARAM_FORMAT,
|
|
FS210X_FORMATS);
|
|
if (ret < 0) {
|
|
dev_err(fs210x->dev,
|
|
"Failed to set hw param format: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (fs210x->devid == FS2105S_DEVICE_ID)
|
|
list = &fs2105s_constraints;
|
|
else
|
|
list = &fs210x_constraints;
|
|
|
|
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_RATE,
|
|
list);
|
|
if (ret < 0) {
|
|
dev_err(fs210x->dev,
|
|
"Failed to set hw param rate: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
|
|
fs210x = snd_soc_component_get_drvdata(dai->component);
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
|
|
case SND_SOC_DAIFMT_CBC_CFC:
|
|
/* Only supports consumer mode */
|
|
break;
|
|
default:
|
|
dev_err(fs210x->dev, "Only supports consumer mode\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_dai_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
int chn_num;
|
|
int ret;
|
|
|
|
if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
|
|
return 0;
|
|
|
|
fs210x = snd_soc_component_get_drvdata(dai->component);
|
|
|
|
fs210x->srate = params_rate(params);
|
|
fs210x->bclk = snd_soc_params_to_bclk(params);
|
|
chn_num = params_channels(params);
|
|
if (chn_num == 1) /* mono */
|
|
fs210x->bclk *= 2; /* I2S bus has 2 channels */
|
|
|
|
/* The FS2105S can't support 16kHz sample rate. */
|
|
if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000)
|
|
return -EOPNOTSUPP;
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
ret = fs210x_set_hw_params(fs210x);
|
|
mutex_unlock(&fs210x->lock);
|
|
if (ret)
|
|
dev_err(fs210x->dev, "Failed to set hw params: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_dai_mute(struct snd_soc_dai *dai, int mute, int stream)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
unsigned long delay;
|
|
|
|
if (stream != SNDRV_PCM_STREAM_PLAYBACK)
|
|
return 0;
|
|
|
|
fs210x = snd_soc_component_get_drvdata(dai->component);
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
|
|
if (!fs210x->is_inited || fs210x->is_suspended) {
|
|
mutex_unlock(&fs210x->lock);
|
|
return 0;
|
|
}
|
|
|
|
mutex_unlock(&fs210x->lock);
|
|
|
|
if (mute) {
|
|
cancel_delayed_work_sync(&fs210x->fault_check_work);
|
|
cancel_delayed_work_sync(&fs210x->start_work);
|
|
} else {
|
|
delay = msecs_to_jiffies(fs210x->check_interval_ms);
|
|
schedule_delayed_work(&fs210x->fault_check_work, delay);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_dai_trigger(struct snd_pcm_substream *substream,
|
|
int cmd, struct snd_soc_dai *dai)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
|
|
fs210x = snd_soc_component_get_drvdata(dai->component);
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
|
|
if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) {
|
|
mutex_unlock(&fs210x->lock);
|
|
return 0;
|
|
}
|
|
|
|
mutex_unlock(&fs210x->lock);
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
/*
|
|
* According to the power up/down sequence of FS210x,
|
|
* it requests the I2S clock has been present
|
|
* and stable(>= 2ms) before playing.
|
|
*/
|
|
schedule_delayed_work(&fs210x->start_work,
|
|
msecs_to_jiffies(FS210X_START_DELAY_MS));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fs210x_start_work(struct work_struct *work)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
int ret;
|
|
|
|
fs210x = container_of(work, struct fs210x_priv, start_work.work);
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
|
|
ret = fs210x_dev_play(fs210x);
|
|
if (ret)
|
|
dev_err(fs210x->dev, "Failed to start playing: %d\n", ret);
|
|
|
|
mutex_unlock(&fs210x->lock);
|
|
}
|
|
|
|
static void fs210x_fault_check_work(struct work_struct *work)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
u16 status;
|
|
int ret;
|
|
|
|
fs210x = container_of(work, struct fs210x_priv, fault_check_work.work);
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
|
|
if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) {
|
|
mutex_unlock(&fs210x->lock);
|
|
return;
|
|
}
|
|
|
|
ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status);
|
|
mutex_unlock(&fs210x->lock);
|
|
if (ret)
|
|
return;
|
|
|
|
if (!(status & FS210X_05H_PVDD_MASK))
|
|
dev_err(fs210x->dev, "PVDD fault\n");
|
|
if (status & FS210X_05H_OCDL_MASK)
|
|
dev_err(fs210x->dev, "OC detected\n");
|
|
if (status & FS210X_05H_UVDL_MASK)
|
|
dev_err(fs210x->dev, "UV detected\n");
|
|
if (status & FS210X_05H_OVDL_MASK)
|
|
dev_err(fs210x->dev, "OV detected\n");
|
|
if (status & FS210X_05H_OTPDL_MASK)
|
|
dev_err(fs210x->dev, "OT detected\n");
|
|
if (status & FS210X_05H_OCRDL_MASK)
|
|
dev_err(fs210x->dev, "OCR detected\n");
|
|
if (status & FS210X_05H_OCLDL_MASK)
|
|
dev_err(fs210x->dev, "OCL detected\n");
|
|
if (status & FS210X_05H_DCRDL_MASK)
|
|
dev_err(fs210x->dev, "DCR detected\n");
|
|
if (status & FS210X_05H_DCLDL_MASK)
|
|
dev_err(fs210x->dev, "DCL detected\n");
|
|
if (status & FS210X_05H_SRDL_MASK)
|
|
dev_err(fs210x->dev, "SR detected\n");
|
|
if (status & FS210X_05H_OTWDL_MASK)
|
|
dev_err(fs210x->dev, "OTW detected\n");
|
|
if (!(status & FS210X_05H_AMPS_MASK))
|
|
dev_dbg(fs210x->dev, "Amplifier unready\n");
|
|
if (!(status & FS210X_05H_PLLS_MASK))
|
|
dev_err(fs210x->dev, "PLL unlock\n");
|
|
if (!(status & FS210X_05H_ANAS_MASK))
|
|
dev_err(fs210x->dev, "Analog power fault\n");
|
|
|
|
schedule_delayed_work(&fs210x->fault_check_work,
|
|
msecs_to_jiffies(fs210x->check_interval_ms));
|
|
}
|
|
|
|
static int fs210x_get_drvdata_from_kctrl(struct snd_kcontrol *kctrl,
|
|
struct fs210x_priv **fs210x)
|
|
{
|
|
struct snd_soc_component *cmpnt;
|
|
|
|
if (!kctrl) {
|
|
pr_err("fs210x: kcontrol is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
cmpnt = snd_soc_kcontrol_component(kctrl);
|
|
if (!cmpnt) {
|
|
pr_err("fs210x: component is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
*fs210x = snd_soc_component_get_drvdata(cmpnt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_effect_scene_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
const struct fs_amp_scene *scene;
|
|
struct fs210x_priv *fs210x;
|
|
const char *name = "N/A";
|
|
int idx, count;
|
|
int ret;
|
|
|
|
ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x);
|
|
if (ret || !fs210x->dev) {
|
|
pr_err("scene_effect_info: fs210x is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
|
uinfo->count = 1;
|
|
|
|
count = fs210x->amp_lib.scene_count - 1; /* Skip init scene */
|
|
if (count < 1) {
|
|
uinfo->value.enumerated.items = 0;
|
|
return 0;
|
|
}
|
|
|
|
uinfo->value.enumerated.items = count;
|
|
if (uinfo->value.enumerated.item >= count)
|
|
uinfo->value.enumerated.item = count - 1;
|
|
|
|
idx = uinfo->value.enumerated.item;
|
|
scene = fs210x->amp_lib.scene + idx + 1;
|
|
if (scene->name)
|
|
name = scene->name;
|
|
|
|
strscpy(uinfo->value.enumerated.name, name, strlen(name) + 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
int index;
|
|
int ret;
|
|
|
|
ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x);
|
|
if (ret || !fs210x->dev) {
|
|
pr_err("scene_effect_get: fs210x is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* The id of effect scene is from 1 to N. */
|
|
if (fs210x->scene_id < 1)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
/*
|
|
* FS210x has scene(s) as below:
|
|
* init scene: id = 0
|
|
* effect scene(s): id = 1~N (optional)
|
|
* effect_index = scene_id - 1
|
|
*/
|
|
index = fs210x->scene_id - 1;
|
|
ucontrol->value.integer.value[0] = index;
|
|
mutex_unlock(&fs210x->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
int scene_id, scene_count;
|
|
bool is_changed = false;
|
|
int ret;
|
|
|
|
ret = fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x);
|
|
if (ret || !fs210x->dev) {
|
|
pr_err("scene_effect_put: fs210x is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
|
|
/*
|
|
* FS210x has scene(s) as below:
|
|
* init scene: id = 0 (It's set in fs210x_init_chip() only)
|
|
* effect scene(s): id = 1~N (optional)
|
|
* scene_id = effect_index + 1.
|
|
*/
|
|
scene_id = ucontrol->value.integer.value[0] + 1;
|
|
scene_count = fs210x->amp_lib.scene_count - 1; /* Skip init scene */
|
|
if (scene_id < 1 || scene_id > scene_count) {
|
|
mutex_unlock(&fs210x->lock);
|
|
return -ERANGE;
|
|
}
|
|
|
|
if (scene_id != fs210x->scene_id)
|
|
is_changed = true;
|
|
|
|
if (fs210x->is_suspended) {
|
|
fs210x->scene_id = scene_id;
|
|
mutex_unlock(&fs210x->lock);
|
|
return is_changed;
|
|
}
|
|
|
|
ret = fs210x_set_scene(fs210x, scene_id);
|
|
if (ret)
|
|
dev_err(fs210x->dev, "Failed to set scene: %d\n", ret);
|
|
|
|
mutex_unlock(&fs210x->lock);
|
|
|
|
if (!ret && is_changed)
|
|
return 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fs210x_playback_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *kc, int event)
|
|
{
|
|
struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm);
|
|
struct fs210x_priv *fs210x = snd_soc_component_get_drvdata(cmpnt);
|
|
int ret = 0;
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
|
|
if (fs210x->is_suspended) {
|
|
mutex_unlock(&fs210x->lock);
|
|
return 0;
|
|
}
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_PRE_PMU:
|
|
/*
|
|
* If there is no bclk for us to set the clock output,
|
|
* we will enable the device(start_work) in dai trigger.
|
|
*/
|
|
if (!fs210x->clk_bclk)
|
|
break;
|
|
fs210x_bclk_set(fs210x, true);
|
|
ret = fs210x_dev_play(fs210x);
|
|
break;
|
|
case SND_SOC_DAPM_POST_PMD:
|
|
ret = fs210x_dev_stop(fs210x);
|
|
fs210x_bclk_set(fs210x, false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&fs210x->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops fs210x_dai_ops = {
|
|
.startup = fs210x_dai_startup,
|
|
.set_fmt = fs210x_dai_set_fmt,
|
|
.hw_params = fs210x_dai_hw_params,
|
|
.mute_stream = fs210x_dai_mute,
|
|
.trigger = fs210x_dai_trigger,
|
|
};
|
|
|
|
static const struct snd_soc_dai_driver fs210x_dai = {
|
|
.name = FS210X_DEFAULT_DAI_NAME,
|
|
.playback = {
|
|
.stream_name = "Playback",
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = FS210X_RATES,
|
|
.formats = FS210X_FORMATS,
|
|
},
|
|
.capture = {
|
|
.stream_name = "Capture",
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = FS210X_RATES,
|
|
.formats = FS210X_FORMATS,
|
|
},
|
|
.ops = &fs210x_dai_ops,
|
|
.symmetric_rate = 1,
|
|
.symmetric_sample_bits = 1,
|
|
};
|
|
|
|
static const DECLARE_TLV_DB_SCALE(fs2105s_vol_tlv, -9709, 19, 1);
|
|
static const DECLARE_TLV_DB_SCALE(fs210x_vol_tlv, -13357, 19, 1);
|
|
|
|
static const struct snd_kcontrol_new fs2105s_vol_control[] = {
|
|
SOC_DOUBLE_R_TLV("PCM Playback Volume",
|
|
FS210X_39H_LVOLCTRL, FS210X_3AH_RVOLCTRL,
|
|
7, 0x1FF, 0, fs2105s_vol_tlv),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new fs210x_vol_control[] = {
|
|
SOC_DOUBLE_R_TLV("PCM Playback Volume",
|
|
FS210X_39H_LVOLCTRL, FS210X_3AH_RVOLCTRL,
|
|
6, 0x2BF, 0, fs210x_vol_tlv),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new fs210x_controls[] = {
|
|
SOC_DOUBLE("DAC Mute Switch", FS210X_30H_DACCTRL, 4, 8, 1, 0),
|
|
SOC_DOUBLE("DAC Fade Switch", FS210X_30H_DACCTRL, 5, 9, 1, 0),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new fs210x_scene_control[] = {
|
|
FS_SOC_ENUM_EXT("Effect Scene",
|
|
fs210x_effect_scene_info,
|
|
fs210x_effect_scene_get,
|
|
fs210x_effect_scene_put),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget fs210x_dapm_widgets[] = {
|
|
SND_SOC_DAPM_AIF_IN_E("AIF IN", "Playback", 0, SND_SOC_NOPM, 0, 0,
|
|
fs210x_playback_event,
|
|
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
|
SND_SOC_DAPM_AIF_OUT("AIF OUT", "Capture", 0, SND_SOC_NOPM, 0, 0),
|
|
SND_SOC_DAPM_OUTPUT("OUTL"),
|
|
SND_SOC_DAPM_OUTPUT("OUTR"),
|
|
SND_SOC_DAPM_INPUT("SDO"),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route fs210x_dapm_routes[] = {
|
|
{ "OUTL", NULL, "AIF IN" },
|
|
{ "OUTR", NULL, "AIF IN" },
|
|
{ "AIF OUT", NULL, "SDO" },
|
|
};
|
|
|
|
static int fs210x_add_mixer_controls(struct fs210x_priv *fs210x,
|
|
struct snd_soc_component *cmpnt)
|
|
{
|
|
const struct snd_kcontrol_new *kctrl;
|
|
int count;
|
|
int ret;
|
|
|
|
if (!fs210x || !cmpnt)
|
|
return -EINVAL;
|
|
|
|
if (fs210x->devid == FS2105S_DEVICE_ID) {
|
|
kctrl = fs2105s_vol_control;
|
|
count = ARRAY_SIZE(fs2105s_vol_control);
|
|
} else {
|
|
kctrl = fs210x_vol_control;
|
|
count = ARRAY_SIZE(fs210x_vol_control);
|
|
}
|
|
|
|
ret = snd_soc_add_component_controls(cmpnt, kctrl, count);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* If the firmware has no scene or only init scene,
|
|
* we skip adding this mixer control.
|
|
*/
|
|
if (fs210x->amp_lib.scene_count < 2)
|
|
return 0;
|
|
|
|
kctrl = fs210x_scene_control;
|
|
count = ARRAY_SIZE(fs210x_scene_control);
|
|
|
|
return snd_soc_add_component_controls(cmpnt, kctrl, count);
|
|
}
|
|
|
|
static int fs210x_probe(struct snd_soc_component *cmpnt)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
int ret;
|
|
|
|
fs210x = snd_soc_component_get_drvdata(cmpnt);
|
|
if (!fs210x || !fs210x->dev)
|
|
return -EINVAL;
|
|
|
|
fs210x->amp_lib.dev = fs210x->dev;
|
|
fs210x->amp_lib.devid = fs210x->devid;
|
|
|
|
ret = fs_amp_load_firmware(&fs210x->amp_lib, fs210x->pdata.fwm_name);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = fs210x_add_mixer_controls(fs210x, cmpnt);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
ret = fs210x_init_chip(fs210x);
|
|
mutex_unlock(&fs210x->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void fs210x_remove(struct snd_soc_component *cmpnt)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
|
|
fs210x = snd_soc_component_get_drvdata(cmpnt);
|
|
if (!fs210x || !fs210x->dev)
|
|
return;
|
|
|
|
cancel_delayed_work_sync(&fs210x->start_work);
|
|
cancel_delayed_work_sync(&fs210x->fault_check_work);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int fs210x_suspend(struct snd_soc_component *cmpnt)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
int ret;
|
|
|
|
fs210x = snd_soc_component_get_drvdata(cmpnt);
|
|
if (!fs210x || !fs210x->dev)
|
|
return -EINVAL;
|
|
|
|
regcache_cache_only(fs210x->regmap, true);
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
fs210x->cur_scene = NULL;
|
|
fs210x->is_inited = false;
|
|
fs210x->is_playing = false;
|
|
fs210x->is_suspended = true;
|
|
|
|
gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */
|
|
fsleep(30000); /* >= 30ms */
|
|
mutex_unlock(&fs210x->lock);
|
|
|
|
cancel_delayed_work_sync(&fs210x->start_work);
|
|
cancel_delayed_work_sync(&fs210x->fault_check_work);
|
|
|
|
ret = regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to suspend: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_resume(struct snd_soc_component *cmpnt)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
int ret;
|
|
|
|
fs210x = snd_soc_component_get_drvdata(cmpnt);
|
|
if (!fs210x || !fs210x->dev)
|
|
return -EINVAL;
|
|
|
|
ret = regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies);
|
|
if (ret) {
|
|
dev_err(fs210x->dev, "Failed to enable supplies: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
mutex_lock(&fs210x->lock);
|
|
|
|
fs210x->is_suspended = false;
|
|
ret = fs210x_init_chip(fs210x);
|
|
|
|
mutex_unlock(&fs210x->lock);
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
#define fs210x_suspend NULL
|
|
#define fs210x_resume NULL
|
|
#endif // CONFIG_PM
|
|
|
|
static bool fs210x_volatile_registers(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case FS210X_00H_STATUS ... FS210X_0FH_I2CADDR:
|
|
case FS210X_ABH_INTSTAT:
|
|
case FS210X_ACH_INTSTATR:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct snd_soc_component_driver fs210x_soc_component_dev = {
|
|
.probe = fs210x_probe,
|
|
.remove = fs210x_remove,
|
|
.suspend = fs210x_suspend,
|
|
.resume = fs210x_resume,
|
|
.controls = fs210x_controls,
|
|
.num_controls = ARRAY_SIZE(fs210x_controls),
|
|
.dapm_widgets = fs210x_dapm_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(fs210x_dapm_widgets),
|
|
.dapm_routes = fs210x_dapm_routes,
|
|
.num_dapm_routes = ARRAY_SIZE(fs210x_dapm_routes),
|
|
};
|
|
|
|
static const struct regmap_config fs210x_regmap = {
|
|
.reg_bits = 8,
|
|
.val_bits = 16,
|
|
.max_register = FS210X_REG_MAX,
|
|
.val_format_endian = REGMAP_ENDIAN_BIG,
|
|
.cache_type = REGCACHE_MAPLE,
|
|
.volatile_reg = fs210x_volatile_registers,
|
|
};
|
|
|
|
static int fs210x_detect_device(struct fs210x_priv *fs210x)
|
|
{
|
|
u16 devid;
|
|
int ret;
|
|
|
|
ret = fs210x_reg_read(fs210x, FS210X_03H_DEVID, &devid);
|
|
if (ret)
|
|
return ret;
|
|
|
|
fs210x->devid = HI_U16(devid);
|
|
|
|
switch (fs210x->devid) {
|
|
case FS210X_DEVICE_ID:
|
|
dev_info(fs210x->dev, "FS2104 detected\n");
|
|
break;
|
|
case FS2105S_DEVICE_ID:
|
|
dev_info(fs210x->dev, "FS2105S detected\n");
|
|
break;
|
|
default:
|
|
dev_err(fs210x->dev, "DEVID: 0x%04X dismatch\n", devid);
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_parse_dts(struct fs210x_priv *fs210x,
|
|
struct fs210x_platform_data *pdata)
|
|
{
|
|
struct device_node *node = fs210x->dev->of_node;
|
|
int i, ret;
|
|
|
|
if (!node)
|
|
return 0;
|
|
|
|
ret = of_property_read_string(node, "firmware-name", &pdata->fwm_name);
|
|
if (ret)
|
|
pdata->fwm_name = FS210X_DEFAULT_FWM_NAME;
|
|
|
|
fs210x->gpio_sdz = devm_gpiod_get_optional(fs210x->dev,
|
|
"reset", GPIOD_OUT_HIGH);
|
|
if (IS_ERR(fs210x->gpio_sdz))
|
|
return dev_err_probe(fs210x->dev, PTR_ERR(fs210x->gpio_sdz),
|
|
"Failed to get reset-gpios\n");
|
|
|
|
for (i = 0; i < FS210X_NUM_SUPPLIES; i++)
|
|
fs210x->supplies[i].supply = fs210x_supply_names[i];
|
|
|
|
ret = devm_regulator_bulk_get(fs210x->dev,
|
|
ARRAY_SIZE(fs210x->supplies),
|
|
fs210x->supplies);
|
|
if (ret)
|
|
return dev_err_probe(fs210x->dev, ret,
|
|
"Failed to get supplies\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fs210x_deinit(struct fs210x_priv *fs210x)
|
|
{
|
|
gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */
|
|
fsleep(10000); /* >= 10ms */
|
|
|
|
regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies);
|
|
}
|
|
|
|
static int fs210x_init(struct fs210x_priv *fs210x)
|
|
{
|
|
int ret;
|
|
|
|
ret = fs210x_parse_dts(fs210x, &fs210x->pdata);
|
|
if (ret)
|
|
return ret;
|
|
|
|
fs210x->clk_bclk = devm_clk_get_optional(fs210x->dev, "bclk");
|
|
if (IS_ERR(fs210x->clk_bclk))
|
|
return dev_err_probe(fs210x->dev, PTR_ERR(fs210x->clk_bclk),
|
|
"Failed to get bclk\n");
|
|
|
|
ret = regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies);
|
|
if (ret)
|
|
return dev_err_probe(fs210x->dev, ret,
|
|
"Failed to enable supplies\n");
|
|
|
|
/* Make sure the SDZ pin is pulled down enough time. */
|
|
fsleep(10000); /* >= 10ms */
|
|
gpiod_set_value_cansleep(fs210x->gpio_sdz, 0); /* Deactivate */
|
|
fsleep(10000); /* >= 10ms */
|
|
|
|
ret = fs210x_detect_device(fs210x);
|
|
if (ret) {
|
|
fs210x_deinit(fs210x);
|
|
return ret;
|
|
}
|
|
|
|
fs210x->scene_id = -1; /* Invalid scene */
|
|
fs210x->cur_scene = NULL;
|
|
fs210x->is_playing = false;
|
|
fs210x->is_inited = false;
|
|
fs210x->is_suspended = false;
|
|
fs210x->check_interval_ms = FS210X_FAULT_CHECK_INTERVAL_MS;
|
|
|
|
INIT_DELAYED_WORK(&fs210x->fault_check_work, fs210x_fault_check_work);
|
|
INIT_DELAYED_WORK(&fs210x->start_work, fs210x_start_work);
|
|
mutex_init(&fs210x->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fs210x_register_snd_component(struct fs210x_priv *fs210x)
|
|
{
|
|
struct snd_soc_dai_driver *dai_drv;
|
|
static int instance_id;
|
|
int ret;
|
|
|
|
dai_drv = devm_kmemdup(fs210x->dev, &fs210x_dai,
|
|
sizeof(fs210x_dai), GFP_KERNEL);
|
|
if (!dai_drv)
|
|
return -ENOMEM;
|
|
|
|
dai_drv->name = devm_kasprintf(fs210x->dev,
|
|
GFP_KERNEL, "%s-%d",
|
|
dai_drv->name, instance_id);
|
|
if (!dai_drv->name)
|
|
return -ENOMEM;
|
|
|
|
instance_id++;
|
|
|
|
if (fs210x->devid == FS2105S_DEVICE_ID) {
|
|
dai_drv->playback.rates = FS2105S_RATES;
|
|
dai_drv->capture.rates = FS2105S_RATES;
|
|
}
|
|
|
|
ret = snd_soc_register_component(fs210x->dev,
|
|
&fs210x_soc_component_dev,
|
|
dai_drv, 1);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t check_interval_ms_show(struct device *dev,
|
|
struct device_attribute *devattr,
|
|
char *buf)
|
|
{
|
|
struct fs210x_priv *fs210x = dev_get_drvdata(dev);
|
|
|
|
return sysfs_emit(buf, "%d\n", fs210x->check_interval_ms);
|
|
}
|
|
|
|
static ssize_t check_interval_ms_store(struct device *dev,
|
|
struct device_attribute *devattr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
struct fs210x_priv *fs210x = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = kstrtouint(buf, 10, &fs210x->check_interval_ms);
|
|
if (ret)
|
|
return -EINVAL;
|
|
|
|
return (ssize_t)count;
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(check_interval_ms);
|
|
|
|
static struct attribute *fs210x_attrs[] = {
|
|
&dev_attr_check_interval_ms.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group fs210x_attr_group = {
|
|
.attrs = fs210x_attrs,
|
|
};
|
|
|
|
static int fs210x_i2c_probe(struct i2c_client *client)
|
|
{
|
|
struct fs210x_priv *fs210x;
|
|
int ret;
|
|
|
|
fs210x = devm_kzalloc(&client->dev, sizeof(*fs210x), GFP_KERNEL);
|
|
if (!fs210x)
|
|
return -ENOMEM;
|
|
|
|
fs210x->i2c = client;
|
|
fs210x->dev = &client->dev;
|
|
i2c_set_clientdata(client, fs210x);
|
|
|
|
fs210x->regmap = devm_regmap_init_i2c(client, &fs210x_regmap);
|
|
if (IS_ERR(fs210x->regmap))
|
|
return dev_err_probe(fs210x->dev, PTR_ERR(fs210x->regmap),
|
|
"Failed to get regmap\n");
|
|
|
|
ret = fs210x_init(fs210x);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = devm_device_add_group(fs210x->dev, &fs210x_attr_group);
|
|
if (ret) {
|
|
fs210x_deinit(fs210x);
|
|
return dev_err_probe(fs210x->dev, ret,
|
|
"Failed to create sysfs group\n");
|
|
}
|
|
|
|
ret = fs210x_register_snd_component(fs210x);
|
|
if (ret) {
|
|
fs210x_deinit(fs210x);
|
|
return dev_err_probe(fs210x->dev, ret,
|
|
"Failed to register component\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fs210x_i2c_remove(struct i2c_client *client)
|
|
{
|
|
struct fs210x_priv *fs210x = i2c_get_clientdata(client);
|
|
|
|
snd_soc_unregister_component(fs210x->dev);
|
|
fs210x_deinit(fs210x);
|
|
}
|
|
|
|
static const struct i2c_device_id fs210x_i2c_id[] = {
|
|
{ "fs2104" },
|
|
{ "fs2105s" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, fs210x_i2c_id);
|
|
|
|
static const struct of_device_id fs210x_of_match[] = {
|
|
{ .compatible = "foursemi,fs2105s", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, fs210x_of_match);
|
|
|
|
static struct i2c_driver fs210x_i2c_driver = {
|
|
.driver = {
|
|
.name = "fs210x",
|
|
.of_match_table = fs210x_of_match,
|
|
},
|
|
.id_table = fs210x_i2c_id,
|
|
.probe = fs210x_i2c_probe,
|
|
.remove = fs210x_i2c_remove,
|
|
};
|
|
|
|
module_i2c_driver(fs210x_i2c_driver);
|
|
|
|
MODULE_AUTHOR("Nick Li <nick.li@foursemi.com>");
|
|
MODULE_DESCRIPTION("FS2104/5S Audio Amplifier Driver");
|
|
MODULE_LICENSE("GPL");
|