diff options
| author | Kees Cook <kees+treewide@kernel.org> | 2026-09-02 15:31:14 -0700 |
|---|---|---|
| committer | Kees Cook <kees@kernel.org> | 2026-09-04 21:37:00 -0700 |
| commit | 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d (patch) | |
| tree | c65086f9bdcd48c6360fb7cb4598bca084da1f32 /sound/soc/codecs/aw88261.c | |
| download | linux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.tar.gz linux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.zip | |
treewide: refresh kmalloc_obj() conversionsgrafted
This is another run of the Coccinelle script for converting kmalloc()
family of allocations to kmalloc_obj() via the existing rules in
scripts/coccinelle/api/kmalloc_objs.cocci
This catches both the set of kmalloc() uses added since the first
kmalloc_obj() conversions in v7.0 and adds a large group missed in the
first pass due to Coccinelle not interacting well with the cleanup.h
scoped_...() family of macros[1]. I worked around this with spatch's
"--macro-file" argument to a file with all the scoped_...() macros mapped
to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control
flow indicator I could find.
Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc,
riscv, and s390 with no new warnings.
Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1]
Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2]
Signed-off-by: Kees Cook <kees+treewide@kernel.org>
Diffstat (limited to 'sound/soc/codecs/aw88261.c')
| -rw-r--r-- | sound/soc/codecs/aw88261.c | 1356 |
1 files changed, 1356 insertions, 0 deletions
diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c new file mode 100644 index 000000000..b8d06534b --- /dev/null +++ b/sound/soc/codecs/aw88261.c @@ -0,0 +1,1356 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// aw88261.c -- AW88261 ALSA SoC Audio driver +// +// Copyright (c) 2023 awinic Technology CO., LTD +// +// Author: Jimmy Zhang <zhangjianming@awinic.com> +// Author: Weidong Wang <wangweidong.a@awinic.com> +// + +#include <linux/cleanup.h> +#include <linux/i2c.h> +#include <linux/firmware.h> +#include <linux/bitops.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include "aw88261.h" +#include "aw88395/aw88395_data_type.h" +#include "aw88395/aw88395_device.h" + +static const struct regmap_config aw88261_remap_config = { + .val_bits = 16, + .reg_bits = 8, + .max_register = AW88261_REG_MAX, + .reg_format_endian = REGMAP_ENDIAN_LITTLE, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static void aw88261_dev_set_volume(struct aw_device *aw_dev, unsigned int value) +{ + unsigned int volume = min(value, (unsigned int)AW88261_MUTE_VOL); + + regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL2_REG, + ~AW88261_VOL_MASK, DB_TO_REG_VAL(volume)); +} + +static void aw88261_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) +{ + if (flag) + regmap_update_bits(aw_dev->regmap, AW88261_I2SCFG1_REG, + ~AW88261_I2STXEN_MASK, AW88261_I2STXEN_ENABLE_VALUE); + else + regmap_update_bits(aw_dev->regmap, AW88261_I2SCFG1_REG, + ~AW88261_I2STXEN_MASK, AW88261_I2STXEN_DISABLE_VALUE); +} + +static void aw88261_dev_pwd(struct aw_device *aw_dev, bool pwd) +{ + if (pwd) + regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, + ~AW88261_PWDN_MASK, AW88261_PWDN_POWER_DOWN_VALUE); + else + regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, + ~AW88261_PWDN_MASK, AW88261_PWDN_WORKING_VALUE); +} + +static void aw88261_dev_amppd(struct aw_device *aw_dev, bool amppd) +{ + if (amppd) + regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, + ~AW88261_AMPPD_MASK, AW88261_AMPPD_POWER_DOWN_VALUE); + else + regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, + ~AW88261_AMPPD_MASK, AW88261_AMPPD_WORKING_VALUE); +} + +static void aw88261_dev_mute(struct aw_device *aw_dev, bool is_mute) +{ + if (is_mute) { + aw88261_dev_set_volume(aw_dev, AW88261_MUTE_VOL); + regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, + ~AW88261_HMUTE_MASK, AW88261_HMUTE_ENABLE_VALUE); + } else { + regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, + ~AW88261_HMUTE_MASK, AW88261_HMUTE_DISABLE_VALUE); + aw88261_dev_set_volume(aw_dev, aw_dev->volume_desc.ctl_volume); + } +} + +static void aw88261_dev_clear_int_status(struct aw_device *aw_dev) +{ + unsigned int int_status; + + /* read int status and clear */ + regmap_read(aw_dev->regmap, AW88261_SYSINT_REG, &int_status); + /* make sure int status is clear */ + regmap_read(aw_dev->regmap, AW88261_SYSINT_REG, &int_status); + + dev_dbg(aw_dev->dev, "read interrupt reg = 0x%04x", int_status); +} + +static int aw88261_dev_get_iis_status(struct aw_device *aw_dev) +{ + unsigned int reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88261_SYSST_REG, ®_val); + if (ret) + return ret; + if ((reg_val & AW88261_BIT_PLL_CHECK) != AW88261_BIT_PLL_CHECK) { + dev_dbg(aw_dev->dev, "check pll lock fail,reg_val:0x%04x", reg_val); + return -EINVAL; + } + + return ret; +} + +static int aw88261_dev_check_pll(struct aw_device *aw_dev) +{ + int ret, i; + + for (i = 0; i < AW88261_DEV_SYSST_CHECK_MAX; i++) { + ret = aw88261_dev_get_iis_status(aw_dev); + if (ret) { + dev_dbg(aw_dev->dev, "mode1 iis signal check error"); + usleep_range(AW88261_2000_US, AW88261_2000_US + 10); + } else { + return ret; + } + } + + return -EPERM; +} + +static int aw88261_dev_configure_syspll(struct aw88261 *aw88261) +{ + struct aw_device *aw_dev = aw88261->aw_pa; + int ret; + + /* Configure TDM slots (I2S is represented as no slots) */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG, + ~AW88261_SLOT_NUM_MASK, aw88261->slot_num_value); + if (ret) + return ret; + + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG, + ~AW88261_I2S_TX_SLOTVLD_MASK, + aw88261->tx_slotvld_mask); + if (ret) + return ret; + + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG, + ~AW88261_I2S_RXL_SLOTVLD_MASK, + aw88261->rxl_slotvld_mask); + if (ret) + return ret; + + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG, + ~AW88261_I2S_RXR_SLOTVLD_MASK, + aw88261->rxr_slotvld_mask); + if (ret) + return ret; + + /* PLL divider must be used for 8/16/32 kHz modes */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_PLLCTRL1_REG, + ~AW88261_CCO_MUX_MASK, aw88261->cco_mux_value); + if (ret) + return ret; + + /* The word clock (WCK) defines the beginning of a frame */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG, + ~AW88261_I2SSR_MASK, aw88261->sr_value); + if (ret) + return ret; + + /* The bit clock (BCK) defines the length of a frame */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG, + ~AW88261_I2SBCK_MASK, + (aw88261->tdm_bck_value != AW88261_TDM_BCK_UNSET) + ? aw88261->tdm_bck_value : aw88261->bck_value); + if (ret) + return ret; + + /* The logical frame size is the width of data for 1 slot */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG, + ~AW88261_I2SFS_MASK, aw88261->fs_value); + if (ret) + return ret; + + /* The I2S interface mode (Philips standard, LSB/MSB justified) */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG, + ~AW88261_I2SMD_MASK, aw88261->md_value); + if (ret) + return ret; + + /* The polarity of the bit clock (BCK) */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, + ~AW88261_BCKINV_MASK, aw88261->bck_inv_value); + if (ret) + return ret; + + return aw88261_dev_check_pll(aw_dev); +} + +static int aw88261_dev_check_sysst(struct aw_device *aw_dev) +{ + unsigned int check_val; + unsigned int reg_val; + int ret, i; + + for (i = 0; i < AW88261_DEV_SYSST_CHECK_MAX; i++) { + ret = regmap_read(aw_dev->regmap, AW88261_SYSST_REG, ®_val); + if (ret) + return ret; + + check_val = reg_val & (~AW88261_BIT_SYSST_CHECK_MASK) + & AW88261_BIT_PLL_CHECK; + if (check_val != AW88261_BIT_PLL_CHECK) { + dev_dbg(aw_dev->dev, "check sysst fail, reg_val=0x%04x, check:0x%x", + reg_val, AW88261_BIT_PLL_CHECK); + usleep_range(AW88261_2000_US, AW88261_2000_US + 10); + } else { + return 0; + } + } + + return -EPERM; +} + +static void aw88261_dev_uls_hmute(struct aw_device *aw_dev, bool uls_hmute) +{ + if (uls_hmute) + regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, + ~AW88261_ULS_HMUTE_MASK, + AW88261_ULS_HMUTE_ENABLE_VALUE); + else + regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, + ~AW88261_ULS_HMUTE_MASK, + AW88261_ULS_HMUTE_DISABLE_VALUE); +} + +static void aw88261_reg_force_set(struct aw88261 *aw88261) +{ + if (aw88261->frcset_en == AW88261_FRCSET_ENABLE) { + /* set FORCE_PWM */ + regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL3_REG, + ~AW88261_FORCE_PWM_MASK, AW88261_FORCE_PWM_FORCEMINUS_PWM_VALUE); + /* set BOOST_OS_WIDTH */ + regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL5_REG, + ~AW88261_BST_OS_WIDTH_MASK, AW88261_BST_OS_WIDTH_50NS_VALUE); + /* set BURST_LOOPR */ + regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL6_REG, + ~AW88261_BST_LOOPR_MASK, AW88261_BST_LOOPR_340K_VALUE); + /* set RSQN_DLY */ + regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL7_REG, + ~AW88261_RSQN_DLY_MASK, AW88261_RSQN_DLY_35NS_VALUE); + /* set BURST_SSMODE */ + regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL8_REG, + ~AW88261_BURST_SSMODE_MASK, AW88261_BURST_SSMODE_FAST_VALUE); + /* set BST_BURST */ + regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL9_REG, + ~AW88261_BST_BURST_MASK, AW88261_BST_BURST_30MA_VALUE); + } else { + dev_dbg(aw88261->aw_pa->dev, "needn't set reg value"); + } +} + +static int aw88261_dev_get_icalk(struct aw_device *aw_dev, int16_t *icalk) +{ + u16 reg_icalk, reg_icalkl; + unsigned int reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88261_EFRH4_REG, ®_val); + if (ret) + return ret; + + reg_icalk = reg_val & (~AW88261_EF_ISN_GESLP_H_MASK); + + ret = regmap_read(aw_dev->regmap, AW88261_EFRL4_REG, ®_val); + if (ret) + return ret; + + reg_icalkl = reg_val & (~AW88261_EF_ISN_GESLP_L_MASK); + + reg_icalk = (reg_icalk >> AW88261_ICALK_SHIFT) & (reg_icalkl >> AW88261_ICALKL_SHIFT); + + if (reg_icalk & (~AW88261_EF_ISN_GESLP_SIGN_MASK)) + reg_icalk = reg_icalk | ~AW88261_EF_ISN_GESLP_NEG; + + *icalk = (int16_t)reg_icalk; + + return ret; +} + +static int aw88261_dev_get_vcalk(struct aw_device *aw_dev, int16_t *vcalk) +{ + u16 reg_vcalk, reg_vcalkl; + unsigned int reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88261_EFRH3_REG, ®_val); + if (ret) + return ret; + + reg_vcalk = (u16)reg_val & (~AW88261_EF_VSN_GESLP_H_MASK); + + ret = regmap_read(aw_dev->regmap, AW88261_EFRL3_REG, ®_val); + if (ret) + return ret; + + reg_vcalkl = (u16)reg_val & (~AW88261_EF_VSN_GESLP_L_MASK); + + reg_vcalk = (reg_vcalk >> AW88261_VCALK_SHIFT) & (reg_vcalkl >> AW88261_VCALKL_SHIFT); + + if (reg_vcalk & AW88261_EF_VSN_GESLP_SIGN_MASK) + reg_vcalk = reg_vcalk | (~AW88261_EF_VSN_GESLP_NEG); + *vcalk = (int16_t)reg_vcalk; + + return ret; +} + +static int aw88261_dev_set_vcalb(struct aw_device *aw_dev) +{ + int16_t icalk_val, vcalk_val; + int icalk, vcalk, vcalb; + u32 reg_val; + int ret; + + ret = aw88261_dev_get_icalk(aw_dev, &icalk_val); + if (ret) + return ret; + + ret = aw88261_dev_get_vcalk(aw_dev, &vcalk_val); + if (ret) + return ret; + + icalk = AW88261_CABL_BASE_VALUE + AW88261_ICABLK_FACTOR * icalk_val; + vcalk = AW88261_CABL_BASE_VALUE + AW88261_VCABLK_FACTOR * vcalk_val; + if (!vcalk) + return -EINVAL; + + vcalb = AW88261_VCAL_FACTOR * icalk / vcalk; + reg_val = (unsigned int)vcalb; + + dev_dbg(aw_dev->dev, "icalk=%d, vcalk=%d, vcalb=%d, reg_val=0x%04x", + icalk, vcalk, vcalb, reg_val); + ret = regmap_write(aw_dev->regmap, AW88261_VSNTM1_REG, reg_val); + + return ret; +} + +static int aw88261_dev_reg_update(struct aw88261 *aw88261, + unsigned char *data, unsigned int len) +{ + struct aw_device *aw_dev = aw88261->aw_pa; + struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; + unsigned int read_val, efcheck_val, read_vol; + int data_len, i, ret; + int16_t *reg_data; + u16 reg_val; + u8 reg_addr; + + if (!len || !data) { + dev_err(aw_dev->dev, "reg data is null or len is 0"); + return -EINVAL; + } + + reg_data = (int16_t *)data; + data_len = len >> 1; + + if (data_len & 0x1) { + dev_err(aw_dev->dev, "data len:%d unsupported", data_len); + return -EINVAL; + } + + for (i = 0; i < data_len; i += 2) { + reg_addr = reg_data[i]; + reg_val = reg_data[i + 1]; + + if (reg_addr == AW88261_SYSCTRL_REG) { + aw88261->amppd_st = reg_val & (~AW88261_AMPPD_MASK); + ret = regmap_read(aw_dev->regmap, reg_addr, &read_val); + if (ret) + break; + + /* keep all three bits from current hw status */ + read_val &= (~AW88261_AMPPD_MASK) | (~AW88261_PWDN_MASK) | + (~AW88261_HMUTE_MASK); + reg_val &= (AW88261_AMPPD_MASK & AW88261_PWDN_MASK & AW88261_HMUTE_MASK); + reg_val |= read_val; + + /* enable uls hmute */ + reg_val &= AW88261_ULS_HMUTE_MASK; + reg_val |= AW88261_ULS_HMUTE_ENABLE_VALUE; + } + + if (reg_addr == AW88261_DBGCTRL_REG) { + efcheck_val = reg_val & (~AW88261_EF_DBMD_MASK); + if (efcheck_val == AW88261_OR_VALUE) + aw88261->efuse_check = AW88261_EF_OR_CHECK; + else + aw88261->efuse_check = AW88261_EF_AND_CHECK; + } + + /* i2stxen */ + if (reg_addr == AW88261_I2SCTRL3_REG) { + /* close tx */ + reg_val &= AW88261_I2STXEN_MASK; + reg_val |= AW88261_I2STXEN_DISABLE_VALUE; + } + + if (reg_addr == AW88261_SYSCTRL2_REG) { + read_vol = (reg_val & (~AW88261_VOL_MASK)) >> + AW88261_VOL_START_BIT; + aw_dev->volume_desc.init_volume = + REG_VAL_TO_DB(read_vol); + } + + if (reg_addr == AW88261_VSNTM1_REG) + continue; + + ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); + if (ret) + break; + } + + ret = aw88261_dev_set_vcalb(aw_dev); + if (ret) + return ret; + + if (aw_dev->prof_cur != aw_dev->prof_index) + vol_desc->ctl_volume = 0; + + /* keep min volume */ + aw88261_dev_set_volume(aw_dev, vol_desc->mute_volume); + + return ret; +} + +static int aw88261_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) +{ + struct aw_prof_info *prof_info = &aw_dev->prof_info; + struct aw_prof_desc *prof_desc; + + if ((index >= aw_dev->prof_info.count) || (index < 0)) { + dev_err(aw_dev->dev, "index[%d] overflow count[%d]", + index, aw_dev->prof_info.count); + return -EINVAL; + } + + prof_desc = &aw_dev->prof_info.prof_desc[index]; + + *prof_name = prof_info->prof_name_list[prof_desc->id]; + + return 0; +} + +static int aw88261_dev_get_prof_data(struct aw_device *aw_dev, int index, + struct aw_prof_desc **prof_desc) +{ + if ((index >= aw_dev->prof_info.count) || (index < 0)) { + dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", + __func__, index, aw_dev->prof_info.count); + return -EINVAL; + } + + *prof_desc = &aw_dev->prof_info.prof_desc[index]; + + return 0; +} + +static int aw88261_dev_fw_update(struct aw88261 *aw88261) +{ + struct aw_device *aw_dev = aw88261->aw_pa; + struct aw_prof_desc *prof_index_desc; + struct aw_sec_data_desc *sec_desc; + char *prof_name; + int ret; + + ret = aw88261_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); + if (ret) { + dev_err(aw_dev->dev, "get prof name failed"); + return -EINVAL; + } + + dev_dbg(aw_dev->dev, "start update %s", prof_name); + + ret = aw88261_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); + if (ret) + return ret; + + /* update reg */ + sec_desc = prof_index_desc->sec_desc; + ret = aw88261_dev_reg_update(aw88261, sec_desc[AW88395_DATA_TYPE_REG].data, + sec_desc[AW88395_DATA_TYPE_REG].len); + if (ret) { + dev_err(aw_dev->dev, "update reg failed"); + return ret; + } + + aw_dev->prof_cur = aw_dev->prof_index; + + return ret; +} + +static int aw88261_dev_start(struct aw88261 *aw88261) +{ + struct aw_device *aw_dev = aw88261->aw_pa; + int ret; + + if (aw_dev->status == AW88261_DEV_PW_ON) { + dev_dbg(aw_dev->dev, "already power on"); + return 0; + } + + /* power on */ + aw88261_dev_pwd(aw_dev, false); + usleep_range(AW88261_2000_US, AW88261_2000_US + 10); + + ret = aw88261_dev_configure_syspll(aw88261); + if (ret) { + dev_dbg(aw_dev->dev, "pll check failed"); + goto pll_check_fail; + } + + /* amppd on */ + aw88261_dev_amppd(aw_dev, false); + usleep_range(AW88261_1000_US, AW88261_1000_US + 50); + + /* check i2s status */ + ret = aw88261_dev_check_sysst(aw_dev); + if (ret) { + dev_dbg(aw_dev->dev, "sysst check failed"); + goto sysst_check_fail; + } + + /* enable tx feedback */ + aw88261_dev_i2s_tx_enable(aw_dev, true); + + if (aw88261->amppd_st) + aw88261_dev_amppd(aw_dev, true); + + aw88261_reg_force_set(aw88261); + + /* close uls mute */ + aw88261_dev_uls_hmute(aw_dev, false); + + /* close mute */ + if (!aw88261->mute_st) + aw88261_dev_mute(aw_dev, false); + + /* clear inturrupt */ + aw88261_dev_clear_int_status(aw_dev); + aw_dev->status = AW88261_DEV_PW_ON; + + return 0; + +sysst_check_fail: + aw88261_dev_i2s_tx_enable(aw_dev, false); + aw88261_dev_clear_int_status(aw_dev); + aw88261_dev_amppd(aw_dev, true); +pll_check_fail: + aw88261_dev_pwd(aw_dev, true); + aw_dev->status = AW88261_DEV_PW_OFF; + + return ret; +} + +static int aw88261_dev_stop(struct aw_device *aw_dev) +{ + if (aw_dev->status == AW88261_DEV_PW_OFF) { + dev_info(aw_dev->dev, "already power off"); + return 0; + } + + aw_dev->status = AW88261_DEV_PW_OFF; + + /* clear inturrupt */ + aw88261_dev_clear_int_status(aw_dev); + + aw88261_dev_uls_hmute(aw_dev, true); + /* set mute */ + aw88261_dev_mute(aw_dev, true); + + /* close tx feedback */ + aw88261_dev_i2s_tx_enable(aw_dev, false); + usleep_range(AW88261_1000_US, AW88261_1000_US + 100); + + /* enable amppd */ + aw88261_dev_amppd(aw_dev, true); + + /* set power down */ + aw88261_dev_pwd(aw_dev, true); + + return 0; +} + +static int aw88261_reg_update(struct aw88261 *aw88261, bool force) +{ + struct aw_device *aw_dev = aw88261->aw_pa; + int ret; + + if (force) { + ret = regmap_write(aw_dev->regmap, + AW88261_ID_REG, AW88261_SOFT_RESET_VALUE); + if (ret) + return ret; + + ret = aw88261_dev_fw_update(aw88261); + if (ret) + return ret; + } else { + if (aw_dev->prof_cur != aw_dev->prof_index) { + ret = aw88261_dev_fw_update(aw88261); + if (ret) + return ret; + } else { + ret = 0; + } + } + + aw_dev->prof_cur = aw_dev->prof_index; + + return ret; +} + +static void aw88261_start_pa(struct aw88261 *aw88261) +{ + int ret, i; + + for (i = 0; i < AW88261_START_RETRIES; i++) { + ret = aw88261_reg_update(aw88261, aw88261->phase_sync); + if (ret) { + dev_dbg(aw88261->aw_pa->dev, + "aw88261_reg_update failed, cnt:%d, ret:%d\n", i, ret); + continue; + } + ret = aw88261_dev_start(aw88261); + if (ret) { + dev_dbg(aw88261->aw_pa->dev, + "aw88261_dev_start failed, cnt:%d, ret:%d\n", i, ret); + continue; + } else { + dev_dbg(aw88261->aw_pa->dev, "start success\n"); + break; + } + } + if (ret != 0) + dev_err(aw88261->aw_pa->dev, "start failure (%d)\n", ret); +} + +static void aw88261_start(struct aw88261 *aw88261) +{ + if (aw88261->aw_pa->fw_status != AW88261_DEV_FW_OK) + return; + + if (aw88261->aw_pa->status == AW88261_DEV_PW_ON) + return; + + aw88261_start_pa(aw88261); +} + +static int aw88261_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + aw88261->bck_inv_value = AW88261_BCKINV_NOT_INVERT_VALUE; + break; + case SND_SOC_DAIFMT_IB_NF: + aw88261->bck_inv_value = AW88261_BCKINV_INVERTED_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported invert mode 0x%x\n", + fmt & SND_SOC_DAIFMT_INV_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_DSP_A: + aw88261->md_value = AW88261_I2SMD_PHILIPS_STANDARD_VALUE; + break; + case SND_SOC_DAIFMT_MSB: + case SND_SOC_DAIFMT_DSP_B: + aw88261->md_value = AW88261_I2SMD_MSB_JUSTIFIED_VALUE; + break; + case SND_SOC_DAIFMT_LSB: + aw88261->md_value = AW88261_I2SMD_LSB_JUSTIFIED_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported DAI format 0x%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + return 0; +} + +static int aw88261_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return 0; + + aw88261->cco_mux_value = AW88261_CCO_MUX_BYPASS_VALUE; + switch (params_rate(params)) { + case 8000: + aw88261->sr_value = AW88261_I2SSR_8KHZ_VALUE; + aw88261->cco_mux_value = AW88261_CCO_MUX_DIVIDED_VALUE; + break; + case 11025: + aw88261->sr_value = AW88261_I2SSR_11P025KHZ_VALUE; + break; + case 12000: + aw88261->sr_value = AW88261_I2SSR_12KHZ_VALUE; + break; + case 16000: + aw88261->sr_value = AW88261_I2SSR_16KHZ_VALUE; + aw88261->cco_mux_value = AW88261_CCO_MUX_DIVIDED_VALUE; + break; + case 22050: + aw88261->sr_value = AW88261_I2SSR_22P05KHZ_VALUE; + break; + case 24000: + aw88261->sr_value = AW88261_I2SSR_24KHZ_VALUE; + break; + case 32000: + aw88261->sr_value = AW88261_I2SSR_32KHZ_VALUE; + aw88261->cco_mux_value = AW88261_CCO_MUX_DIVIDED_VALUE; + break; + case 44100: + aw88261->sr_value = AW88261_I2SSR_44P1KHZ_VALUE; + break; + case 48000: + aw88261->sr_value = AW88261_I2SSR_48KHZ_VALUE; + break; + case 96000: + aw88261->sr_value = AW88261_I2SSR_96KHZ_VALUE; + break; + case 192000: + aw88261->sr_value = AW88261_I2SSR_192KHZ_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported sample rate %d\n", + params_rate(params)); + return -EINVAL; + } + + switch (params_width(params)) { + case 16: + aw88261->fs_value = AW88261_I2SFS_16_BITS_VALUE; + break; + case 20: + aw88261->fs_value = AW88261_I2SFS_20_BITS_VALUE; + break; + case 24: + aw88261->fs_value = AW88261_I2SFS_24_BITS_VALUE; + break; + case 32: + aw88261->fs_value = AW88261_I2SFS_32_BITS_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported bit width %d\n", + params_width(params)); + return -EINVAL; + } + + switch (params_physical_width(params)) { + case 16: + aw88261->bck_value = AW88261_I2SBCK_32FS_VALUE; + break; + case 24: + aw88261->bck_value = AW88261_I2SBCK_48FS_VALUE; + break; + case 32: + aw88261->bck_value = AW88261_I2SBCK_64FS_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported physical bit width %d\n", + params_physical_width(params)); + return -EINVAL; + } + + return 0; +} + +static int aw88261_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + int chan; + + switch (slots) { + case 0: + /* Just reset everything TDM related to I2S values */ + aw88261->slot_num_value = AW88261_SLOT_NUM_I2S_MODE_VALUE; + aw88261->tdm_bck_value = AW88261_TDM_BCK_UNSET; + aw88261->tx_slotvld_mask = 0 << AW88261_I2S_TX_SLOTVLD_START_BIT; + aw88261->rxl_slotvld_mask = 0 << AW88261_I2S_RXL_SLOTVLD_START_BIT; + aw88261->rxr_slotvld_mask = 1 << AW88261_I2S_RXR_SLOTVLD_START_BIT; + return 0; + case 1: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM1S_VALUE; + break; + case 2: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM2S_VALUE; + break; + case 4: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM4S_VALUE; + break; + case 6: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM6S_VALUE; + break; + case 8: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM8S_VALUE; + break; + case 16: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM16S_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported slot count %d\n", slots); + return -EINVAL; + } + + switch (slot_width) { + case 16: + aw88261->tdm_bck_value = AW88261_I2SBCK_32FS_VALUE; + break; + case 20: + case 24: + aw88261->tdm_bck_value = AW88261_I2SBCK_48FS_VALUE; + break; + case 32: + aw88261->tdm_bck_value = AW88261_I2SBCK_64FS_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported slot width %d\n", + slot_width); + return -EINVAL; + } + + if (tx_mask != 0) { + if ((chan = __ffs(tx_mask)) > 16) + return -EINVAL; + + aw88261->tx_slotvld_mask = chan << AW88261_I2S_TX_SLOTVLD_START_BIT; + } + + if (rx_mask != 0) { + if ((chan = __ffs(rx_mask)) > 16) + return -EINVAL; + + aw88261->rxl_slotvld_mask = chan << AW88261_I2S_RXL_SLOTVLD_START_BIT; + } + + if ((rx_mask & ~BIT(chan)) != 0) { + if ((chan = __ffs(rx_mask & ~BIT(chan))) > 16) + return -EINVAL; + + aw88261->rxr_slotvld_mask = chan << AW88261_I2S_RXR_SLOTVLD_START_BIT; + } + + return 0; +} + +static const struct snd_soc_dai_ops aw88261_dai_ops = { + .set_fmt = aw88261_set_fmt, + .hw_params = aw88261_hw_params, + .set_tdm_slot = aw88261_set_tdm_slot, +}; + +static struct snd_soc_dai_driver aw88261_dai[] = { + { + .name = "aw88261-aif", + .id = 1, + .playback = { + .stream_name = "Speaker_Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AW88261_RATES, + .formats = AW88261_FORMATS, + }, + .capture = { + .stream_name = "Speaker_Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AW88261_RATES, + .formats = AW88261_FORMATS, + }, + .ops = &aw88261_dai_ops, + }, +}; + +static int aw88261_dev_set_profile_index(struct aw_device *aw_dev, int index) +{ + /* check the index whether is valid */ + if ((index >= aw_dev->prof_info.count) || (index < 0)) + return -EINVAL; + /* check the index whether change */ + if (aw_dev->prof_index == index) + return -EPERM; + + aw_dev->prof_index = index; + + return 0; +} + +static int aw88261_profile_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + char *prof_name; + int count, ret; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + + count = aw88261->aw_pa->prof_info.count; + if (count <= 0) { + uinfo->value.enumerated.items = 0; + return 0; + } + + uinfo->value.enumerated.items = count; + + if (uinfo->value.enumerated.item >= count) + uinfo->value.enumerated.item = count - 1; + + count = uinfo->value.enumerated.item; + + ret = aw88261_dev_get_prof_name(aw88261->aw_pa, count, &prof_name); + if (ret) { + strscpy(uinfo->value.enumerated.name, "null"); + return 0; + } + + strscpy(uinfo->value.enumerated.name, prof_name); + + return 0; +} + +static int aw88261_profile_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + + ucontrol->value.integer.value[0] = aw88261->aw_pa->prof_index; + + return 0; +} + +static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + int ret; + + /* pa stop or stopping just set profile */ + guard(mutex)(&aw88261->lock); + ret = aw88261_dev_set_profile_index(aw88261->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); + return 0; + } + + if (aw88261->aw_pa->status) { + aw88261_dev_stop(aw88261->aw_pa); + aw88261_start(aw88261); + } + + return 1; +} + +static int aw88261_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + struct aw_volume_desc *vol_desc = &aw88261->aw_pa->volume_desc; + + ucontrol->value.integer.value[0] = + (AW88261_MUTE_VOL - vol_desc->ctl_volume) / 2; + + return 0; +} + +static int aw88261_volume_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + struct aw_volume_desc *vol_desc = &aw88261->aw_pa->volume_desc; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int value = ucontrol->value.integer.value[0]; + + if (value < mc->min || value > mc->max) + return -EINVAL; + + value = AW88261_MUTE_VOL - (value * 2); + + if (vol_desc->ctl_volume != value) { + vol_desc->ctl_volume = value; + aw88261_dev_set_volume(aw88261->aw_pa, vol_desc->ctl_volume); + + return 1; + } + + return 0; +} + +/* + * The field contains 4 bits in units of 6dB + 6 bits in units of 0.125dB + * which is too precise for TLV (!) so we have to multiply the scale by 2. + * + * The range is clamped at -90dB to prevent overflowing the 4-bit part. + */ +static const DECLARE_TLV_DB_SCALE(volume_tlv, -9000, 25, 0); + +static const struct snd_kcontrol_new aw88261_controls[] = { + SOC_SINGLE_EXT_TLV("PCM Playback Volume", AW88261_SYSCTRL2_REG, + 6, AW88261_CTL_MAX_VOL, 1, + aw88261_volume_get, aw88261_volume_set, volume_tlv), + AW88261_PROFILE_EXT("Profile Set", aw88261_profile_info, + aw88261_profile_get, aw88261_profile_set), +}; + +static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + + guard(mutex)(&aw88261->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88261_start(aw88261); + break; + case SND_SOC_DAPM_POST_PMD: + aw88261_dev_stop(aw88261->aw_pa); + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget aw88261_dapm_widgets[] = { + /* playback */ + SND_SOC_DAPM_AIF_IN_E("AIF_RX", "Speaker_Playback", 0, 0, 0, 0, + aw88261_playback_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUTPUT("DAC Output"), + + /* capture */ + SND_SOC_DAPM_AIF_OUT("AIF_TX", "Speaker_Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_INPUT("ADC Input"), +}; + +static const struct snd_soc_dapm_route aw88261_audio_map[] = { + {"DAC Output", NULL, "AIF_RX"}, + {"AIF_TX", NULL, "ADC Input"}, +}; + +static int aw88261_frcset_check(struct aw88261 *aw88261) +{ + unsigned int reg_val; + u16 temh, teml, tem; + int ret; + + ret = regmap_read(aw88261->regmap, AW88261_EFRH3_REG, ®_val); + if (ret) + return ret; + temh = ((u16)reg_val & (~AW88261_TEMH_MASK)); + + ret = regmap_read(aw88261->regmap, AW88261_EFRL3_REG, ®_val); + if (ret) + return ret; + teml = ((u16)reg_val & (~AW88261_TEML_MASK)); + + if (aw88261->efuse_check == AW88261_EF_OR_CHECK) + tem = (temh | teml); + else + tem = (temh & teml); + + if (tem == AW88261_DEFAULT_CFG) + aw88261->frcset_en = AW88261_FRCSET_ENABLE; + else + aw88261->frcset_en = AW88261_FRCSET_DISABLE; + + dev_dbg(aw88261->aw_pa->dev, "tem is 0x%04x, frcset_en is %d", + tem, aw88261->frcset_en); + + return ret; +} + +static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg) +{ + struct aw_device *aw_dev = aw88261->aw_pa; + int ret; + + ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); + if (ret) { + dev_err(aw_dev->dev, "aw_dev acf parse failed"); + return -EINVAL; + } + + ret = regmap_write(aw_dev->regmap, AW88261_ID_REG, AW88261_SOFT_RESET_VALUE); + if (ret) + return ret; + + aw_dev->prof_cur = AW88261_INIT_PROFILE; + aw_dev->prof_index = AW88261_INIT_PROFILE; + + ret = aw88261_dev_fw_update(aw88261); + if (ret) { + dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); + return ret; + } + + ret = aw88261_frcset_check(aw88261); + if (ret) { + dev_err(aw_dev->dev, "aw88261_frcset_check ret = %d\n", ret); + return ret; + } + + aw88261_dev_clear_int_status(aw_dev); + + aw88261_dev_uls_hmute(aw_dev, true); + + aw88261_dev_mute(aw_dev, true); + + aw88261_dev_i2s_tx_enable(aw_dev, false); + + usleep_range(AW88261_1000_US, AW88261_1000_US + 100); + + aw88261_dev_amppd(aw_dev, true); + + aw88261_dev_pwd(aw_dev, true); + + return 0; +} + +static int aw88261_request_firmware_file(struct aw88261 *aw88261) +{ + const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + const char *fw_name; + int ret; + + aw88261->aw_pa->fw_status = AW88261_DEV_FW_FAILED; + + ret = device_property_read_string(aw88261->aw_pa->dev, "firmware-name", &fw_name); + if (ret) + fw_name = AW88261_ACF_FILE; + + ret = request_firmware(&cont, fw_name, aw88261->aw_pa->dev); + if (ret) + return dev_err_probe(aw88261->aw_pa->dev, ret, + "load [%s] failed!", fw_name); + + dev_info(aw88261->aw_pa->dev, "loaded %s - size: %zu\n", + fw_name, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); + if (!aw_cfg) + return -ENOMEM; + + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); + + aw88261->aw_cfg = aw_cfg; + + ret = aw88395_dev_load_acf_check(aw88261->aw_pa, aw88261->aw_cfg); + if (ret) { + dev_err(aw88261->aw_pa->dev, "load [%s] failed !", fw_name); + return ret; + } + + scoped_guard(mutex, &aw88261->lock) { + /* aw device init */ + ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); + if (ret) + dev_err(aw88261->aw_pa->dev, "dev init failed"); + } + + return ret; +} + +static int aw88261_codec_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + int ret; + + ret = aw88261_request_firmware_file(aw88261); + if (ret) + return dev_err_probe(aw88261->aw_pa->dev, ret, + "aw88261_request_firmware_file failed\n"); + + /* add widgets */ + ret = snd_soc_dapm_new_controls(dapm, aw88261_dapm_widgets, + ARRAY_SIZE(aw88261_dapm_widgets)); + if (ret) + return ret; + + /* add route */ + ret = snd_soc_dapm_add_routes(dapm, aw88261_audio_map, + ARRAY_SIZE(aw88261_audio_map)); + if (ret) + return ret; + + ret = snd_soc_add_component_controls(component, aw88261_controls, + ARRAY_SIZE(aw88261_controls)); + + return ret; +} + +static const struct snd_soc_component_driver soc_codec_dev_aw88261 = { + .probe = aw88261_codec_probe, +}; + +static void aw88261_parse_channel_dt(struct aw88261 *aw88261) +{ + struct aw_device *aw_dev = aw88261->aw_pa; + struct device_node *np = aw_dev->dev->of_node; + u32 channel_value = AW88261_DEV_DEFAULT_CH; + + of_property_read_u32(np, "awinic,audio-channel", &channel_value); + aw88261->phase_sync = of_property_read_bool(np, "awinic,sync-flag"); + + aw_dev->channel = channel_value; +} + +static int aw88261_init(struct aw88261 *aw88261, struct i2c_client *i2c, struct regmap *regmap) +{ + struct aw_device *aw_dev; + unsigned int chip_id; + int ret; + + ret = devm_regulator_get_enable(&i2c->dev, "dvdd"); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Failed to enable dvdd supply\n"); + + /* read chip id */ + ret = regmap_read(regmap, AW88261_ID_REG, &chip_id); + if (ret) { + dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); + return ret; + } + if (chip_id != AW88261_CHIP_ID) { + dev_err(&i2c->dev, "unsupported device id = %x", chip_id); + return -ENXIO; + } + + dev_info(&i2c->dev, "chip id = %x\n", chip_id); + + aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); + if (!aw_dev) + return -ENOMEM; + + aw88261->aw_pa = aw_dev; + aw_dev->i2c = i2c; + aw_dev->regmap = regmap; + aw_dev->dev = &i2c->dev; + aw_dev->chip_id = AW88261_CHIP_ID; + aw_dev->acf = NULL; + aw_dev->prof_info.prof_desc = NULL; + aw_dev->prof_info.count = 0; + aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; + aw_dev->channel = 0; + aw_dev->fw_status = AW88261_DEV_FW_FAILED; + aw_dev->volume_desc.ctl_volume = AW88261_CTL_DEFAULT_VOL; + aw_dev->volume_desc.mute_volume = AW88261_MUTE_VOL; + aw88261_parse_channel_dt(aw88261); + + return ret; +} + +static int aw88261_i2c_probe(struct i2c_client *i2c) +{ + struct aw88261 *aw88261; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C)) + return dev_err_probe(&i2c->dev, -ENXIO, "check_functionality failed"); + + aw88261 = devm_kzalloc(&i2c->dev, sizeof(*aw88261), GFP_KERNEL); + if (!aw88261) + return -ENOMEM; + + /* set defaults */ + aw88261->slot_num_value = AW88261_SLOT_NUM_I2S_MODE_VALUE; + aw88261->sr_value = AW88261_I2SSR_48KHZ_VALUE; + aw88261->cco_mux_value = AW88261_CCO_MUX_BYPASS_VALUE; + aw88261->fs_value = AW88261_I2SFS_24_BITS_VALUE; + aw88261->bck_value = AW88261_I2SBCK_64FS_VALUE; + aw88261->bck_inv_value = AW88261_BCKINV_NOT_INVERT_VALUE; + aw88261->tdm_bck_value = AW88261_TDM_BCK_UNSET; + aw88261->md_value = AW88261_I2SMD_PHILIPS_STANDARD_VALUE; + aw88261->rxr_slotvld_mask = 1 << AW88261_I2S_RXR_SLOTVLD_START_BIT; + + mutex_init(&aw88261->lock); + + i2c_set_clientdata(i2c, aw88261); + + aw88261->regmap = devm_regmap_init_i2c(i2c, &aw88261_remap_config); + if (IS_ERR(aw88261->regmap)) { + ret = PTR_ERR(aw88261->regmap); + return dev_err_probe(&i2c->dev, ret, "failed to init regmap: %d\n", ret); + } + + /* aw pa init */ + ret = aw88261_init(aw88261, i2c, aw88261->regmap); + if (ret) + return ret; + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_codec_dev_aw88261, + aw88261_dai, ARRAY_SIZE(aw88261_dai)); + if (ret) + dev_err(&i2c->dev, "failed to register aw88261: %d", ret); + + return ret; +} + +static const struct i2c_device_id aw88261_i2c_id[] = { + { .name = "aw88261" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aw88261_i2c_id); + +static const struct of_device_id aw88261_of_table[] = { + { .compatible = "awinic,aw88261" }, + { } +}; +MODULE_DEVICE_TABLE(of, aw88261_of_table); + +static struct i2c_driver aw88261_i2c_driver = { + .driver = { + .name = "aw88261", + .of_match_table = aw88261_of_table, + }, + .probe = aw88261_i2c_probe, + .id_table = aw88261_i2c_id, +}; +module_i2c_driver(aw88261_i2c_driver); + +MODULE_DESCRIPTION("ASoC AW88261 Smart PA Driver"); +MODULE_LICENSE("GPL v2"); |
