Message ID | 20230818085558.1431-1-shenghao-ding@ti.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | [v3,1/2] ALSA: hda/tas2781: Add tas2781 HDA driver | expand |
The code doesn't look too bad but needs a bit more work. There are quite a few error handling issues, pm_runtime needs to be revisited and ACPI/EFI as well. > +enum calib_data { tas2781_calib_data? > + R0_VAL = 0, > + INV_R0, > + R0LOW, > + POWER, > + TLIM, > + CALIB_MAX > +}; > + > +static int tas2781_get_i2c_res(struct acpi_resource *ares, void *data) > +{ > + struct tasdevice_priv *tas_priv = data; > + struct acpi_resource_i2c_serialbus *sb; > + > + if (i2c_acpi_get_i2c_resource(ares, &sb)) { > + if (tas_priv->ndev < TASDEVICE_MAX_CHANNELS && > + sb->slave_address != TAS2781_GLOBAL_ADDR) { > + tas_priv->tasdevice[tas_priv->ndev].dev_addr = > + (unsigned int)sb->slave_address; > + tas_priv->ndev++; > + } > + } > + return 1; > +} > + > +static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) > +{ > + struct acpi_device *adev; > + struct device *physdev; > + LIST_HEAD(resources); > + const char *sub; > + int ret; > + > + adev = acpi_dev_get_first_match_dev(hid, NULL, -1); > + if (!adev) { > + dev_err(p->dev, > + "Failed to find an ACPI device for %s\n", hid); > + return -ENODEV; > + } [1] need to take care of a resource leak here > + ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p); > + if (ret < 0) > + goto err; you return without doing acpi_dev_put(adev), and you are also doing a put_device(physdev) which is not initialized yet. NAK, this needs to be reworked since a simple... > + > + acpi_dev_free_resource_list(&resources); > + strscpy(p->dev_name, hid, sizeof(p->dev_name)); > + physdev = get_device(acpi_get_first_physical_node(adev)); > + acpi_dev_put(adev); ... move ong those last two lines to [1] > + > + /* No side-effect to the playback even if subsystem_id is NULL*/ > + sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); > + if (IS_ERR(sub)) > + sub = NULL; > + > + p->acpi_subsystem_id = sub; > + > + put_device(physdev); > + > + return 0; > + > +err: > + dev_err(p->dev, "read acpi error, ret: %d\n", ret); > + put_device(physdev); > + > + return ret; > +} > + > +static void tas2781_hda_playback_hook(struct device *dev, int action) > +{ > + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); > + > + dev_dbg(tas_priv->dev, "%s: action = %d\n", __func__, action); > + switch (action) { > + case HDA_GEN_PCM_ACT_OPEN: > + pm_runtime_get_sync(dev); test if this actually works? > + mutex_lock(&tas_priv->codec_lock); > + tasdevice_tuning_switch(tas_priv, 0); > + mutex_unlock(&tas_priv->codec_lock); > + break; > + case HDA_GEN_PCM_ACT_CLOSE: > + mutex_lock(&tas_priv->codec_lock); > + tasdevice_tuning_switch(tas_priv, 1); > + mutex_unlock(&tas_priv->codec_lock); how useful is this codec_lock, is the 'action' not protected at a higher level? > + > + pm_runtime_mark_last_busy(dev); > + pm_runtime_put_autosuspend(dev); > + break; > + default: > + dev_dbg(tas_priv->dev, "Playback action not supported: %d\n", > + action); > + break; > + } > +} > +static int tasdevice_hda_clamp(int val, int max) > +{ > + if (val > max) > + val = max; > + > + if (val < 0) > + val = 0; > + return val; > +} I've seen that macro in the TAS2783 code as well, that sounds like a good helper function to share? > + > +static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *ucontrol) > +{ > + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); > + int nr_profile = ucontrol->value.integer.value[0]; > + int max = tas_priv->rcabin.ncfgs - 1; > + int val, ret = 0; > + > + val = tasdevice_hda_clamp(nr_profile, max); > + > + if (tas_priv->rcabin.profile_cfg_id != nr_profile) { > + tas_priv->rcabin.profile_cfg_id = nr_profile; > + ret = 1; return 1; > + } > + > + return ret; return 0; you don't really need a variable here. same comment for other usages. > +static void tas2781_apply_calib(struct tasdevice_priv *tas_priv) > +{ > + static const unsigned char page_array[CALIB_MAX] = { > + 0x17, 0x18, 0x18, 0x0d, 0x18 > + }; > + static const unsigned char rgno_array[CALIB_MAX] = { > + 0x74, 0x0c, 0x14, 0x3c, 0x7c > + }; > + unsigned char *data; > + int i, j, rc; > + > + for (i = 0; i < tas_priv->ndev; i++) { > + data = tas_priv->cali_data.data + > + i * TASDEVICE_SPEAKER_CALIBRATION_SIZE; > + for (j = 0; j < CALIB_MAX; j++) { > + rc = tasdevice_dev_bulk_write(tas_priv, i, > + TASDEVICE_REG(0, page_array[j], rgno_array[j]), > + &(data[4 * j]), 4); > + if (rc < 0) > + dev_err(tas_priv->dev, > + "chn %d calib %d bulk_wr err = %d\n", > + i, j, rc); do you want to keep going or just stop on the first error? > + } > + } > +} > + > +/* Update the calibrate data, including speaker impedance, f0, etc, into algo. > + * Calibrate data is done by manufacturer in the factory. These data are used > + * by Algo for calucating the speaker temperature, speaker membrance excursion typos, use a spell checker. calculation membrane > + * and f0 in real time during playback. > + */ > +static int tas2781_save_calibration(struct tasdevice_priv *tas_priv) > +{ > + efi_guid_t efi_guid = EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, > + 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3); I've seen an EFI use for SoundWire TAS2783, is this the same thing? It looks very very similar except that this one checks the BUFFER_TOO_SMALL. If yes, can this be a helper function? If this is the same sort of calibration we should not have duplicated code really, it's easier to maintain if there's one set of helpers shared between TI drivers. > + static efi_char16_t efi_name[] = L"CALI_DATA"; > + struct tm *tm = &tas_priv->tm; > + unsigned int attr, crc; > + unsigned int *tmp_val; > + efi_status_t status; > + > + /* Lenovo devices */ > + if (tas_priv->catlog_id == LENOVO) > + efi_guid = EFI_GUID(0x1f52d2a1, 0xbb3a, 0x457d, 0xbc, 0x09, > + 0x43, 0xa3, 0xf4, 0x31, 0x0a, 0x92); > + > + tas_priv->cali_data.total_sz = 0; > + /* Get real size of UEFI variable */ > + status = efi.get_variable(efi_name, &efi_guid, &attr, > + &tas_priv->cali_data.total_sz, tas_priv->cali_data.data); > + if (status == EFI_BUFFER_TOO_SMALL) { > + /* Allocate data buffer of data_size bytes */ > + tas_priv->cali_data.data = devm_kzalloc(tas_priv->dev, > + tas_priv->cali_data.total_sz, GFP_KERNEL); > + if (!tas_priv->cali_data.data) > + return -ENOMEM; > + /* Get variable contents into buffer */ > + status = efi.get_variable(efi_name, &efi_guid, &attr, > + &tas_priv->cali_data.total_sz, > + tas_priv->cali_data.data); > + if (status != EFI_SUCCESS) > + return -EINVAL; > + } > + > + tmp_val = (unsigned int *)tas_priv->cali_data.data; > + > + crc = crc32(~0, tas_priv->cali_data.data, 84) ^ ~0; > + dev_dbg(tas_priv->dev, "cali crc 0x%08x PK tmp_val 0x%08x\n", > + crc, tmp_val[21]); > + > + if (crc == tmp_val[21]) { > + time64_to_tm(tmp_val[20], 0, tm); > + dev_dbg(tas_priv->dev, "%4ld-%2d-%2d, %2d:%2d:%2d\n", > + tm->tm_year, tm->tm_mon, tm->tm_mday, > + tm->tm_hour, tm->tm_min, tm->tm_sec); > + tas2781_apply_calib(tas_priv); > + } else > + tas_priv->cali_data.total_sz = 0; > + > + return 0; > +} > + > +static void tasdev_fw_ready(const struct firmware *fmw, void *context) > +{ > + struct tasdevice_priv *tas_priv = context; > + struct hda_codec *codec = tas_priv->codec; > + int i, ret; > + > + pm_runtime_get_sync(tas_priv->dev); test that it worked? > + mutex_lock(&tas_priv->codec_lock); ... > +static int tas2781_hda_bind(struct device *dev, struct device *master, > + void *master_data) > +{ > + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); > + struct hda_component *comps = master_data; > + struct hda_codec *codec; > + unsigned int subid; > + int ret; > + > + if (!comps || tas_priv->index < 0 || > + tas_priv->index >= HDA_MAX_COMPONENTS) > + return -EINVAL; > + > + comps = &comps[tas_priv->index]; > + if (comps->dev) > + return -EBUSY; > + > + codec = comps->codec; > + subid = codec->core.subsystem_id >> 16; > + > + switch (subid) { > + case 0x17aa: magic number should be a define somewhere... > + tas_priv->catlog_id = LENOVO; > + break; > + default: > + tas_priv->catlog_id = OTHERS; > + break; > + } > + > + pm_runtime_get_sync(dev); test that it worked? > + > + comps->dev = dev; > + > + strscpy(comps->name, dev_name(dev), sizeof(comps->name)); > + > + ret = tascodec_init(tas_priv, codec, tasdev_fw_ready); > + if (ret) > + return ret; need to do a put_autosuspend below, this is leaking a refcount. > + > + comps->playback_hook = tas2781_hda_playback_hook; > + > + pm_runtime_mark_last_busy(dev); > + pm_runtime_put_autosuspend(dev); > + > + return 0; > +} > + > +static void tas2781_hda_unbind(struct device *dev, > + struct device *master, void *master_data) > +{ > + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); > + struct hda_component *comps = master_data; > + > + if (comps[tas_priv->index].dev == dev) > + memset(&comps[tas_priv->index], 0, sizeof(*comps)); > + > + tasdevice_config_info_remove(tas_priv); > + tasdevice_dsp_remove(tas_priv); > + > + tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING; > +} > + > +static const struct component_ops tas2781_hda_comp_ops = { > + .bind = tas2781_hda_bind, > + .unbind = tas2781_hda_unbind, > +}; > + > +static void tas2781_hda_remove(struct device *dev) > +{ > + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); > + > + pm_runtime_get_sync(tas_priv->dev); > + pm_runtime_disable(tas_priv->dev); I don't think this sequence makes any sense. > + component_del(tas_priv->dev, &tas2781_hda_comp_ops); > + > + pm_runtime_put_noidle(tas_priv->dev); > + > + tasdevice_remove(tas_priv); > +} > + > +static int tas2781_hda_i2c_probe(struct i2c_client *clt) > +{ > + struct tasdevice_priv *tas_priv; > + const char *device_name; > + int ret; > + > + if (strstr(dev_name(&clt->dev), "TIAS2781")) > + device_name = "TIAS2781"; > + else > + return -ENODEV; > + > + tas_priv = tasdevice_kzalloc(clt); > + if (!tas_priv) > + return -ENOMEM; > + > + tas_priv->irq_info.irq = clt->irq; > + ret = tas2781_read_acpi(tas_priv, device_name); > + if (ret) > + return dev_err_probe(tas_priv->dev, ret, > + "Platform not supported\n"); > + > + ret = tasdevice_init(tas_priv); > + if (ret) > + goto err; > + > + pm_runtime_set_autosuspend_delay(tas_priv->dev, 3000); > + pm_runtime_use_autosuspend(tas_priv->dev); > + pm_runtime_mark_last_busy(tas_priv->dev); > + pm_runtime_set_active(tas_priv->dev); > + pm_runtime_get_noresume(tas_priv->dev); this .. > + pm_runtime_enable(tas_priv->dev); > + > + pm_runtime_put_autosuspend(tas_priv->dev); and this should be removed IMHO. it makes no sense to me. > + > + ret = component_add(tas_priv->dev, &tas2781_hda_comp_ops); > + if (ret) { > + dev_err(tas_priv->dev, "Register component failed: %d\n", ret); > + pm_runtime_disable(tas_priv->dev); > + goto err; > + } > + > + tas2781_reset(tas_priv); > +err: > + if (ret) > + tas2781_hda_remove(&clt->dev); > + return ret; > +} > + > +static void tas2781_hda_i2c_remove(struct i2c_client *clt) > +{ > + tas2781_hda_remove(&clt->dev); so for symmetry that's where pm_runtime needs to be disabled. > +static int tas2781_system_suspend(struct device *dev) > +{ > + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); > + int ret; > + > + dev_dbg(tas_priv->dev, "System Suspend\n"); > + > + ret = pm_runtime_force_suspend(dev); > + if (ret) > + return ret; that's usually the other way around, for system suspend you either want the device to be pm_runtime active, or if it's already suspended do nothing. This is very odd to me. > + > + /* Shutdown chip before system suspend */ > + regcache_cache_only(tas_priv->regmap, false); > + tasdevice_tuning_switch(tas_priv, 1); > + regcache_cache_only(tas_priv->regmap, true); > + regcache_mark_dirty(tas_priv->regmap); > + > + /* > + * Reset GPIO may be shared, so cannot reset here. > + * However beyond this point, amps may be powered down. > + */ > + return 0; > +} > + > +static int tas2781_system_resume(struct device *dev) > +{ > + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); > + unsigned long calib_data_sz = > + tas_priv->ndev * TASDEVICE_SPEAKER_CALIBRATION_SIZE; > + int i, ret; > + > + dev_dbg(tas_priv->dev, "System Resume\n"); > + > + ret = pm_runtime_force_resume(dev); > + if (ret) > + return ret; that's also not quite right IMHO, this doesn't follow the recommended sequences for power management. > + > + mutex_lock(&tas_priv->codec_lock); > + > + 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; > + } > + tas2781_reset(tas_priv); > + tasdevice_prmg_load(tas_priv, tas_priv->cur_prog); > + > + /* If calibrated data occurs error, dsp will still work with default > + * calibrated data inside algo. > + */ > + if (tas_priv->cali_data.total_sz > calib_data_sz) > + tas2781_apply_calib(tas_priv); > + mutex_unlock(&tas_priv->codec_lock); > + > + return 0; > +} > +MODULE_IMPORT_NS(SND_SOC_TAS2781_FMWLIB);
On Fri, 18 Aug 2023 18:30:42 +0200, Pierre-Louis Bossart wrote: > > The code doesn't look too bad but needs a bit more work. There are quite > a few error handling issues, pm_runtime needs to be revisited and > ACPI/EFI as well. > > > +enum calib_data { > > tas2781_calib_data? Well, as long as it's a local stuff, a suffix isn't really needed. If it makes thing too confusing, it should be named properly, of course, though. > > +static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) > > +{ > > + struct acpi_device *adev; > > + struct device *physdev; > > + LIST_HEAD(resources); > > + const char *sub; > > + int ret; > > + > > + adev = acpi_dev_get_first_match_dev(hid, NULL, -1); > > + if (!adev) { > > + dev_err(p->dev, > > + "Failed to find an ACPI device for %s\n", hid); > > + return -ENODEV; > > + } > > [1] need to take care of a resource leak here Right, and that's rather a typo at the end of the function... > > +err: > > + dev_err(p->dev, "read acpi error, ret: %d\n", ret); > > + put_device(physdev); ... this must be put_device(adev) instead physdev. > > +static void tas2781_hda_playback_hook(struct device *dev, int action) > > +{ > > + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); > > + > > + dev_dbg(tas_priv->dev, "%s: action = %d\n", __func__, action); > > + switch (action) { > > + case HDA_GEN_PCM_ACT_OPEN: > > + pm_runtime_get_sync(dev); > > test if this actually works? To be fair, most of driver codes don't check it, including the HD-audio core. (Actually, over 900 of 1300 calls have no check in the whole tree.) It implies that forcing the check in each place is moot; rather the helper needs to be coded not to fail, IMO. > > +static int tasdevice_hda_clamp(int val, int max) > > +{ > > + if (val > max) > > + val = max; > > + > > + if (val < 0) > > + val = 0; > > + return val; > > +} > > I've seen that macro in the TAS2783 code as well, that sounds like a > good helper function to share? There is already clamp() macro, and I guess it can be replaced with clamp(val, 0, max). > > + > > + comps->dev = dev; > > + > > + strscpy(comps->name, dev_name(dev), sizeof(comps->name)); > > + > > + ret = tascodec_init(tas_priv, codec, tasdev_fw_ready); > > + if (ret) > > + return ret; > > need to do a put_autosuspend below, this is leaking a refcount. Right, that needs an obvious leak. Let's fix it. > > +static int tas2781_system_suspend(struct device *dev) > > +{ > > + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); > > + int ret; > > + > > + dev_dbg(tas_priv->dev, "System Suspend\n"); > > + > > + ret = pm_runtime_force_suspend(dev); > > + if (ret) > > + return ret; > > that's usually the other way around, for system suspend you either want > the device to be pm_runtime active, or if it's already suspended do nothing. > > This is very odd to me. This is a normal procedure, as stated in pm_runtime_force_suspend() definition: /** * pm_runtime_force_suspend - Force a device into suspend state if needed. .... * Typically this function may be invoked from a system suspend callback to make * sure the device is put into low power state and it should only be used during * system-wide PM transitions to sleep states. It assumes that the analogous * pm_runtime_force_resume() will be used to resume the device. thanks, Takashi
>>> +static void tas2781_hda_playback_hook(struct device *dev, int action) >>> +{ >>> + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); >>> + >>> + dev_dbg(tas_priv->dev, "%s: action = %d\n", __func__, action); >>> + switch (action) { >>> + case HDA_GEN_PCM_ACT_OPEN: >>> + pm_runtime_get_sync(dev); >> >> test if this actually works? > > To be fair, most of driver codes don't check it, including the > HD-audio core. (Actually, over 900 of 1300 calls have no check in the > whole tree.) > > It implies that forcing the check in each place is moot; rather the > helper needs to be coded not to fail, IMO. Maybe that's true for HDaudio, for the SoundWire parts we absolutely need to detect if the resume worked. There are more steps involved, the clock-stop mode entry/exit, context restoration, re-enumeration, etc. I think it'd be a mistake to sit on our hands and assume the world is perfect. We have to track cases where the codec isn't properly resumed and prevent it from accessing resources that are just unavailable. >>> +static int tas2781_system_suspend(struct device *dev) >>> +{ >>> + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); >>> + int ret; >>> + >>> + dev_dbg(tas_priv->dev, "System Suspend\n"); >>> + >>> + ret = pm_runtime_force_suspend(dev); >>> + if (ret) >>> + return ret; >> >> that's usually the other way around, for system suspend you either want >> the device to be pm_runtime active, or if it's already suspended do nothing. >> >> This is very odd to me. > > This is a normal procedure, as stated in pm_runtime_force_suspend() > definition: > > /** > * pm_runtime_force_suspend - Force a device into suspend state if needed. > .... > * Typically this function may be invoked from a system suspend callback to make > * sure the device is put into low power state and it should only be used during > * system-wide PM transitions to sleep states. It assumes that the analogous > * pm_runtime_force_resume() will be used to resume the device. It's possible that it's fine for HDaudio, it wouldn't work in all cases for SoundWire where we have to make sure all pm_runtime suspended devices are brought back to D0 and then the regular system suspend happens. That's mainly because pm_runtime suspend relies on clock stop and system suspend does not. In other words, this isn't a generic solution at all.
On Mon, 21 Aug 2023 16:43:31 +0200, Pierre-Louis Bossart wrote: > > > >>> +static void tas2781_hda_playback_hook(struct device *dev, int action) > >>> +{ > >>> + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); > >>> + > >>> + dev_dbg(tas_priv->dev, "%s: action = %d\n", __func__, action); > >>> + switch (action) { > >>> + case HDA_GEN_PCM_ACT_OPEN: > >>> + pm_runtime_get_sync(dev); > >> > >> test if this actually works? > > > > To be fair, most of driver codes don't check it, including the > > HD-audio core. (Actually, over 900 of 1300 calls have no check in the > > whole tree.) > > > > It implies that forcing the check in each place is moot; rather the > > helper needs to be coded not to fail, IMO. > > Maybe that's true for HDaudio, for the SoundWire parts we absolutely > need to detect if the resume worked. There are more steps involved, the > clock-stop mode entry/exit, context restoration, re-enumeration, etc. > > I think it'd be a mistake to sit on our hands and assume the world is > perfect. We have to track cases where the codec isn't properly resumed > and prevent it from accessing resources that are just unavailable. Yeah, I don't mean that it's wrong or bad to have the check. The check should be there. But, I feel that it's time to rather switch to the proper call. Basically pm_runtime_resume_and_get() is the better alternative (except for its long naming), and we may think of converting the whole. > >>> +static int tas2781_system_suspend(struct device *dev) > >>> +{ > >>> + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); > >>> + int ret; > >>> + > >>> + dev_dbg(tas_priv->dev, "System Suspend\n"); > >>> + > >>> + ret = pm_runtime_force_suspend(dev); > >>> + if (ret) > >>> + return ret; > >> > >> that's usually the other way around, for system suspend you either want > >> the device to be pm_runtime active, or if it's already suspended do nothing. > >> > >> This is very odd to me. > > > > This is a normal procedure, as stated in pm_runtime_force_suspend() > > definition: > > > > /** > > * pm_runtime_force_suspend - Force a device into suspend state if needed. > > .... > > * Typically this function may be invoked from a system suspend callback to make > > * sure the device is put into low power state and it should only be used during > > * system-wide PM transitions to sleep states. It assumes that the analogous > > * pm_runtime_force_resume() will be used to resume the device. > > It's possible that it's fine for HDaudio, it wouldn't work in all cases > for SoundWire where we have to make sure all pm_runtime suspended > devices are brought back to D0 and then the regular system suspend > happens. That's mainly because pm_runtime suspend relies on clock stop > and system suspend does not. > > In other words, this isn't a generic solution at all. Well, I suppose rather that soundwire is an exception :) For majority of devices, the system suspend/resume is nothing but pm_runtime_force_*() calls. e.g. take a look at DEFINE_RUNTIME_DEV_PM_OPS() in linux/pm_runtime.h. Takashi
On 8/21/23 09:57, Takashi Iwai wrote: > On Mon, 21 Aug 2023 16:43:31 +0200, > Pierre-Louis Bossart wrote: >> >> >>>>> +static void tas2781_hda_playback_hook(struct device *dev, int action) >>>>> +{ >>>>> + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); >>>>> + >>>>> + dev_dbg(tas_priv->dev, "%s: action = %d\n", __func__, action); >>>>> + switch (action) { >>>>> + case HDA_GEN_PCM_ACT_OPEN: >>>>> + pm_runtime_get_sync(dev); >>>> >>>> test if this actually works? >>> >>> To be fair, most of driver codes don't check it, including the >>> HD-audio core. (Actually, over 900 of 1300 calls have no check in the >>> whole tree.) >>> >>> It implies that forcing the check in each place is moot; rather the >>> helper needs to be coded not to fail, IMO. >> >> Maybe that's true for HDaudio, for the SoundWire parts we absolutely >> need to detect if the resume worked. There are more steps involved, the >> clock-stop mode entry/exit, context restoration, re-enumeration, etc. >> >> I think it'd be a mistake to sit on our hands and assume the world is >> perfect. We have to track cases where the codec isn't properly resumed >> and prevent it from accessing resources that are just unavailable. > > Yeah, I don't mean that it's wrong or bad to have the check. The > check should be there. > > But, I feel that it's time to rather switch to the proper call. > Basically pm_runtime_resume_and_get() is the better alternative > (except for its long naming), and we may think of converting the > whole. Oh, I broke so many drivers by trying a well-indented conversion to pm_runtime_resume_and_get(). The flow is different wrt -EACCESs and we ended-up with multiple errors. In hindsight I wish we had left the legacy code alone. >>>>> +static int tas2781_system_suspend(struct device *dev) >>>>> +{ >>>>> + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); >>>>> + int ret; >>>>> + >>>>> + dev_dbg(tas_priv->dev, "System Suspend\n"); >>>>> + >>>>> + ret = pm_runtime_force_suspend(dev); >>>>> + if (ret) >>>>> + return ret; >>>> >>>> that's usually the other way around, for system suspend you either want >>>> the device to be pm_runtime active, or if it's already suspended do nothing. >>>> >>>> This is very odd to me. >>> >>> This is a normal procedure, as stated in pm_runtime_force_suspend() >>> definition: >>> >>> /** >>> * pm_runtime_force_suspend - Force a device into suspend state if needed. >>> .... >>> * Typically this function may be invoked from a system suspend callback to make >>> * sure the device is put into low power state and it should only be used during >>> * system-wide PM transitions to sleep states. It assumes that the analogous >>> * pm_runtime_force_resume() will be used to resume the device. >> >> It's possible that it's fine for HDaudio, it wouldn't work in all cases >> for SoundWire where we have to make sure all pm_runtime suspended >> devices are brought back to D0 and then the regular system suspend >> happens. That's mainly because pm_runtime suspend relies on clock stop >> and system suspend does not. >> >> In other words, this isn't a generic solution at all. > > Well, I suppose rather that soundwire is an exception :) > > For majority of devices, the system suspend/resume is nothing but > pm_runtime_force_*() calls. e.g. take a look at > DEFINE_RUNTIME_DEV_PM_OPS() in linux/pm_runtime.h. I guess you are right. SoundWire has indeed these quirky modes and differences between SOC vendors that will force us to be extra careful in what the codec driver implements.
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index 886255a03e8b..e66257277492 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -130,6 +130,21 @@ config SND_HDA_SCODEC_CS35L41_SPI comment "Set to Y if you want auto-loading the side codec driver" depends on SND_HDA=y && SND_HDA_SCODEC_CS35L41_SPI=m +config SND_HDA_SCODEC_TAS2781_I2C + tristate "Build TAS2781 HD-audio side codec support for I2C Bus" + depends on I2C + depends on ACPI + depends on SND_SOC + select SND_SOC_TAS2781_COMLIB + select SND_SOC_TAS2781_FMWLIB + select CRC32_SARWATE + help + Say Y or M here to include TAS2781 I2C HD-audio side codec support + in snd-hda-intel driver, such as ALC287. + +comment "Set to Y if you want auto-loading the side codec driver" + depends on SND_HDA=y && SND_HDA_SCODEC_TAS2781_I2C=m + config SND_HDA_CODEC_REALTEK tristate "Build Realtek HD-audio codec support" select SND_HDA_GENERIC diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index 00d306104484..1c76609690d6 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -32,6 +32,7 @@ snd-hda-scodec-cs35l41-objs := cs35l41_hda.o snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o snd-hda-cs-dsp-ctls-objs := hda_cs_dsp_ctl.o +snd-hda-scodec-tas2781-i2c-objs := tas2781_hda_i2c.o # common driver obj-$(CONFIG_SND_HDA) := snd-hda-codec.o @@ -56,6 +57,7 @@ obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o +obj-$(CONFIG_SND_HDA_SCODEC_TAS2781_I2C) += snd-hda-scodec-tas2781-i2c.o # this must be the last entry after codec drivers; # otherwise the codec patches won't be hooked before the PCI probe diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c new file mode 100644 index 000000000000..4ccbe12ad451 --- /dev/null +++ b/sound/pci/hda/tas2781_hda_i2c.c @@ -0,0 +1,858 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// TAS2781 HDA I2C driver +// +// Copyright 2023 Texas Instruments, Inc. +// +// Author: Shenghao Ding <shenghao-ding@ti.com> + +#include <linux/acpi.h> +#include <linux/crc8.h> +#include <linux/crc32.h> +#include <linux/efi.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/hda_codec.h> +#include <sound/soc.h> +#include <sound/tas2781.h> +#include <sound/tlv.h> +#include <sound/tas2781-tlv.h> + +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_component.h" +#include "hda_jack.h" +#include "hda_generic.h" + +#define TASDEVICE_SPEAKER_CALIBRATION_SIZE 20 + +/* No standard control callbacks for SNDRV_CTL_ELEM_IFACE_CARD + * Define two controls, one is Volume control callbacks, the other is + * flag setting control callbacks. + */ + +/* Volume control callbacks for tas2781 */ +#define ACARD_SINGLE_RANGE_EXT_TLV(xname, xreg, xshift, xmin, xmax, xinvert, \ + xhandler_get, xhandler_put, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = (xname),\ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_range, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = xreg, .rreg = xreg, .shift = xshift, \ + .rshift = xshift, .min = xmin, .max = xmax, \ + .invert = xinvert} } + +/* Flag control callbacks for tas2781 */ +#define ACARD_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = xname, \ + .info = snd_ctl_boolean_mono_info, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = xdata } + +enum calib_data { + R0_VAL = 0, + INV_R0, + R0LOW, + POWER, + TLIM, + CALIB_MAX +}; + +static int tas2781_get_i2c_res(struct acpi_resource *ares, void *data) +{ + struct tasdevice_priv *tas_priv = data; + struct acpi_resource_i2c_serialbus *sb; + + if (i2c_acpi_get_i2c_resource(ares, &sb)) { + if (tas_priv->ndev < TASDEVICE_MAX_CHANNELS && + sb->slave_address != TAS2781_GLOBAL_ADDR) { + tas_priv->tasdevice[tas_priv->ndev].dev_addr = + (unsigned int)sb->slave_address; + tas_priv->ndev++; + } + } + return 1; +} + +static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) +{ + struct acpi_device *adev; + struct device *physdev; + LIST_HEAD(resources); + const char *sub; + int ret; + + adev = acpi_dev_get_first_match_dev(hid, NULL, -1); + if (!adev) { + dev_err(p->dev, + "Failed to find an ACPI device for %s\n", hid); + return -ENODEV; + } + + ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p); + if (ret < 0) + goto err; + + acpi_dev_free_resource_list(&resources); + strscpy(p->dev_name, hid, sizeof(p->dev_name)); + physdev = get_device(acpi_get_first_physical_node(adev)); + acpi_dev_put(adev); + + /* No side-effect to the playback even if subsystem_id is NULL*/ + sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); + if (IS_ERR(sub)) + sub = NULL; + + p->acpi_subsystem_id = sub; + + put_device(physdev); + + return 0; + +err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); + put_device(physdev); + + return ret; +} + +static void tas2781_hda_playback_hook(struct device *dev, int action) +{ + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); + + dev_dbg(tas_priv->dev, "%s: action = %d\n", __func__, action); + switch (action) { + case HDA_GEN_PCM_ACT_OPEN: + pm_runtime_get_sync(dev); + mutex_lock(&tas_priv->codec_lock); + tasdevice_tuning_switch(tas_priv, 0); + mutex_unlock(&tas_priv->codec_lock); + break; + case HDA_GEN_PCM_ACT_CLOSE: + mutex_lock(&tas_priv->codec_lock); + tasdevice_tuning_switch(tas_priv, 1); + mutex_unlock(&tas_priv->codec_lock); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + break; + default: + dev_dbg(tas_priv->dev, "Playback action not supported: %d\n", + action); + break; + } +} + +static int tasdevice_info_profile(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = tas_priv->rcabin.ncfgs - 1; + + return 0; +} + +static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = tas_priv->rcabin.profile_cfg_id; + + return 0; +} + +static int tasdevice_hda_clamp(int val, int max) +{ + if (val > max) + val = max; + + if (val < 0) + val = 0; + return val; +} + +static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + int nr_profile = ucontrol->value.integer.value[0]; + int max = tas_priv->rcabin.ncfgs - 1; + int val, ret = 0; + + val = tasdevice_hda_clamp(nr_profile, max); + + if (tas_priv->rcabin.profile_cfg_id != nr_profile) { + tas_priv->rcabin.profile_cfg_id = nr_profile; + ret = 1; + } + + return ret; +} + +static int tasdevice_info_programs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct tasdevice_fw *tas_fw = tas_priv->fmw; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = tas_fw->nr_programs - 1; + + return 0; +} + +static int tasdevice_info_config(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct tasdevice_fw *tas_fw = tas_priv->fmw; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = tas_fw->nr_configurations - 1; + + return 0; +} + +static int tasdevice_program_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = tas_priv->cur_prog; + + return 0; +} + +static int tasdevice_program_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct tasdevice_fw *tas_fw = tas_priv->fmw; + int nr_program = ucontrol->value.integer.value[0]; + int max = tas_fw->nr_programs - 1; + int val, ret = 0; + + val = tasdevice_hda_clamp(nr_program, max); + + if (tas_priv->cur_prog != nr_program) { + tas_priv->cur_prog = nr_program; + ret = 1; + } + + return ret; +} + +static int tasdevice_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = tas_priv->cur_conf; + + return 0; +} + +static int tasdevice_config_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct tasdevice_fw *tas_fw = tas_priv->fmw; + int nr_config = ucontrol->value.integer.value[0]; + int max = tas_fw->nr_configurations - 1; + int val, ret = 0; + + val = tasdevice_hda_clamp(nr_config, max); + + if (tas_priv->cur_conf != nr_config) { + tas_priv->cur_conf = nr_config; + ret = 1; + } + + return ret; +} + +/* + * tas2781_digital_getvol - get the volum control + * @kcontrol: control pointer + * @ucontrol: User data + * Customer Kcontrol for tas2781 is primarily for regmap booking, paging + * depends on internal regmap mechanism. + * tas2781 contains book and page two-level register map, especially + * book switching will set the register BXXP00R7F, after switching to the + * correct book, then leverage the mechanism for paging to access the + * register. + */ +static int tas2781_digital_getvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + return tasdevice_digital_getvol(tas_priv, ucontrol, mc); +} + +static int tas2781_amp_getvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + return tasdevice_amp_getvol(tas_priv, ucontrol, mc); +} + +static int tas2781_digital_putvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + /* The check of the given value is in tasdevice_digital_putvol. */ + return tasdevice_digital_putvol(tas_priv, ucontrol, mc); +} + +static int tas2781_amp_putvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + /* The check of the given value is in tasdevice_amp_putvol. */ + return tasdevice_amp_putvol(tas_priv, ucontrol, mc); +} + +static int tas2781_force_fwload_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = (int)tas_priv->force_fwload_status; + dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__, + tas_priv->force_fwload_status ? "ON" : "OFF"); + + return 0; +} + +static int tas2781_force_fwload_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol); + bool change, val = (bool)ucontrol->value.integer.value[0]; + + if (tas_priv->force_fwload_status == val) + change = false; + else { + change = true; + tas_priv->force_fwload_status = val; + } + dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__, + tas_priv->force_fwload_status ? "ON" : "OFF"); + + return change; +} + +static const struct snd_kcontrol_new tas2781_snd_controls[] = { + ACARD_SINGLE_RANGE_EXT_TLV("Speaker Analog Gain", TAS2781_AMP_LEVEL, + 1, 0, 20, 0, tas2781_amp_getvol, + tas2781_amp_putvol, amp_vol_tlv), + ACARD_SINGLE_RANGE_EXT_TLV("Speaker Digital Gain", TAS2781_DVC_LVL, + 0, 0, 200, 1, tas2781_digital_getvol, + tas2781_digital_putvol, dvc_tlv), + ACARD_SINGLE_BOOL_EXT("Speaker Force Firmware Load", 0, + tas2781_force_fwload_get, tas2781_force_fwload_put), +}; + +static const struct snd_kcontrol_new tas2781_prof_ctrl = { + .name = "Speaker Profile Id", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = tasdevice_info_profile, + .get = tasdevice_get_profile_id, + .put = tasdevice_set_profile_id, +}; + +static const struct snd_kcontrol_new tas2781_dsp_prog_ctrl = { + .name = "Speaker Program Id", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = tasdevice_info_programs, + .get = tasdevice_program_get, + .put = tasdevice_program_put, +}; + +static const struct snd_kcontrol_new tas2781_dsp_conf_ctrl = { + .name = "Speaker Config Id", + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = tasdevice_info_config, + .get = tasdevice_config_get, + .put = tasdevice_config_put, +}; + +static void tas2781_apply_calib(struct tasdevice_priv *tas_priv) +{ + static const unsigned char page_array[CALIB_MAX] = { + 0x17, 0x18, 0x18, 0x0d, 0x18 + }; + static const unsigned char rgno_array[CALIB_MAX] = { + 0x74, 0x0c, 0x14, 0x3c, 0x7c + }; + unsigned char *data; + int i, j, rc; + + for (i = 0; i < tas_priv->ndev; i++) { + data = tas_priv->cali_data.data + + i * TASDEVICE_SPEAKER_CALIBRATION_SIZE; + for (j = 0; j < CALIB_MAX; j++) { + rc = tasdevice_dev_bulk_write(tas_priv, i, + TASDEVICE_REG(0, page_array[j], rgno_array[j]), + &(data[4 * j]), 4); + if (rc < 0) + dev_err(tas_priv->dev, + "chn %d calib %d bulk_wr err = %d\n", + i, j, rc); + } + } +} + +/* Update the calibrate data, including speaker impedance, f0, etc, into algo. + * Calibrate data is done by manufacturer in the factory. These data are used + * by Algo for calucating the speaker temperature, speaker membrance excursion + * and f0 in real time during playback. + */ +static int tas2781_save_calibration(struct tasdevice_priv *tas_priv) +{ + efi_guid_t efi_guid = EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, + 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3); + static efi_char16_t efi_name[] = L"CALI_DATA"; + struct tm *tm = &tas_priv->tm; + unsigned int attr, crc; + unsigned int *tmp_val; + efi_status_t status; + + /* Lenovo devices */ + if (tas_priv->catlog_id == LENOVO) + efi_guid = EFI_GUID(0x1f52d2a1, 0xbb3a, 0x457d, 0xbc, 0x09, + 0x43, 0xa3, 0xf4, 0x31, 0x0a, 0x92); + + tas_priv->cali_data.total_sz = 0; + /* Get real size of UEFI variable */ + status = efi.get_variable(efi_name, &efi_guid, &attr, + &tas_priv->cali_data.total_sz, tas_priv->cali_data.data); + if (status == EFI_BUFFER_TOO_SMALL) { + /* Allocate data buffer of data_size bytes */ + tas_priv->cali_data.data = devm_kzalloc(tas_priv->dev, + tas_priv->cali_data.total_sz, GFP_KERNEL); + if (!tas_priv->cali_data.data) + return -ENOMEM; + /* Get variable contents into buffer */ + status = efi.get_variable(efi_name, &efi_guid, &attr, + &tas_priv->cali_data.total_sz, + tas_priv->cali_data.data); + if (status != EFI_SUCCESS) + return -EINVAL; + } + + tmp_val = (unsigned int *)tas_priv->cali_data.data; + + crc = crc32(~0, tas_priv->cali_data.data, 84) ^ ~0; + dev_dbg(tas_priv->dev, "cali crc 0x%08x PK tmp_val 0x%08x\n", + crc, tmp_val[21]); + + if (crc == tmp_val[21]) { + time64_to_tm(tmp_val[20], 0, tm); + dev_dbg(tas_priv->dev, "%4ld-%2d-%2d, %2d:%2d:%2d\n", + tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + tas2781_apply_calib(tas_priv); + } else + tas_priv->cali_data.total_sz = 0; + + return 0; +} + +static void tasdev_fw_ready(const struct firmware *fmw, void *context) +{ + struct tasdevice_priv *tas_priv = context; + struct hda_codec *codec = tas_priv->codec; + int i, ret; + + pm_runtime_get_sync(tas_priv->dev); + mutex_lock(&tas_priv->codec_lock); + + ret = tasdevice_rca_parser(tas_priv, fmw); + if (ret) + goto out; + + ret = snd_ctl_add(codec->card, + snd_ctl_new1(&tas2781_prof_ctrl, tas_priv)); + if (ret) { + dev_err(tas_priv->dev, + "Failed to add KControl %s = %d\n", + tas2781_prof_ctrl.name, ret); + goto out; + } + + for (i = 0; i < ARRAY_SIZE(tas2781_snd_controls); i++) { + ret = snd_ctl_add(codec->card, + snd_ctl_new1(&tas2781_snd_controls[i], tas_priv)); + if (ret) { + dev_err(tas_priv->dev, + "Failed to add KControl %s = %d\n", + tas2781_snd_controls[i].name, ret); + goto out; + } + } + + tasdevice_dsp_remove(tas_priv); + + tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING; + scnprintf(tas_priv->coef_binaryname, 64, "TAS2XXX%04X.bin", + codec->core.subsystem_id & 0xffff); + ret = tasdevice_dsp_parser(tas_priv); + if (ret) { + dev_err(tas_priv->dev, "dspfw load %s error\n", + tas_priv->coef_binaryname); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + goto out; + } + + ret = snd_ctl_add(codec->card, + snd_ctl_new1(&tas2781_dsp_prog_ctrl, tas_priv)); + if (ret) { + dev_err(tas_priv->dev, + "Failed to add KControl %s = %d\n", + tas2781_dsp_prog_ctrl.name, ret); + goto out; + } + + ret = snd_ctl_add(codec->card, + snd_ctl_new1(&tas2781_dsp_conf_ctrl, tas_priv)); + if (ret) { + dev_err(tas_priv->dev, + "Failed to add KControl %s = %d\n", + tas2781_dsp_conf_ctrl.name, ret); + goto out; + } + + tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; + tasdevice_prmg_load(tas_priv, 0); + + /* If calibrated data occurs error, dsp will still works with default + * calibrated data inside algo. + */ + tas2781_save_calibration(tas_priv); + +out: + if (tas_priv->fw_state == TASDEVICE_DSP_FW_FAIL) { + /*If DSP FW fail, kcontrol won't be created */ + tasdevice_config_info_remove(tas_priv); + tasdevice_dsp_remove(tas_priv); + } + mutex_unlock(&tas_priv->codec_lock); + if (fmw) + release_firmware(fmw); + pm_runtime_mark_last_busy(tas_priv->dev); + pm_runtime_put_autosuspend(tas_priv->dev); +} + +static int tas2781_hda_bind(struct device *dev, struct device *master, + void *master_data) +{ + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); + struct hda_component *comps = master_data; + struct hda_codec *codec; + unsigned int subid; + int ret; + + if (!comps || tas_priv->index < 0 || + tas_priv->index >= HDA_MAX_COMPONENTS) + return -EINVAL; + + comps = &comps[tas_priv->index]; + if (comps->dev) + return -EBUSY; + + codec = comps->codec; + subid = codec->core.subsystem_id >> 16; + + switch (subid) { + case 0x17aa: + tas_priv->catlog_id = LENOVO; + break; + default: + tas_priv->catlog_id = OTHERS; + break; + } + + pm_runtime_get_sync(dev); + + comps->dev = dev; + + strscpy(comps->name, dev_name(dev), sizeof(comps->name)); + + ret = tascodec_init(tas_priv, codec, tasdev_fw_ready); + if (ret) + return ret; + + comps->playback_hook = tas2781_hda_playback_hook; + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static void tas2781_hda_unbind(struct device *dev, + struct device *master, void *master_data) +{ + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); + struct hda_component *comps = master_data; + + if (comps[tas_priv->index].dev == dev) + memset(&comps[tas_priv->index], 0, sizeof(*comps)); + + tasdevice_config_info_remove(tas_priv); + tasdevice_dsp_remove(tas_priv); + + tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING; +} + +static const struct component_ops tas2781_hda_comp_ops = { + .bind = tas2781_hda_bind, + .unbind = tas2781_hda_unbind, +}; + +static void tas2781_hda_remove(struct device *dev) +{ + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); + + pm_runtime_get_sync(tas_priv->dev); + pm_runtime_disable(tas_priv->dev); + + component_del(tas_priv->dev, &tas2781_hda_comp_ops); + + pm_runtime_put_noidle(tas_priv->dev); + + tasdevice_remove(tas_priv); +} + +static int tas2781_hda_i2c_probe(struct i2c_client *clt) +{ + struct tasdevice_priv *tas_priv; + const char *device_name; + int ret; + + if (strstr(dev_name(&clt->dev), "TIAS2781")) + device_name = "TIAS2781"; + else + return -ENODEV; + + tas_priv = tasdevice_kzalloc(clt); + if (!tas_priv) + return -ENOMEM; + + tas_priv->irq_info.irq = clt->irq; + ret = tas2781_read_acpi(tas_priv, device_name); + if (ret) + return dev_err_probe(tas_priv->dev, ret, + "Platform not supported\n"); + + ret = tasdevice_init(tas_priv); + if (ret) + goto err; + + pm_runtime_set_autosuspend_delay(tas_priv->dev, 3000); + pm_runtime_use_autosuspend(tas_priv->dev); + pm_runtime_mark_last_busy(tas_priv->dev); + pm_runtime_set_active(tas_priv->dev); + pm_runtime_get_noresume(tas_priv->dev); + pm_runtime_enable(tas_priv->dev); + + pm_runtime_put_autosuspend(tas_priv->dev); + + ret = component_add(tas_priv->dev, &tas2781_hda_comp_ops); + if (ret) { + dev_err(tas_priv->dev, "Register component failed: %d\n", ret); + pm_runtime_disable(tas_priv->dev); + goto err; + } + + tas2781_reset(tas_priv); +err: + if (ret) + tas2781_hda_remove(&clt->dev); + return ret; +} + +static void tas2781_hda_i2c_remove(struct i2c_client *clt) +{ + tas2781_hda_remove(&clt->dev); +} + +static int tas2781_runtime_suspend(struct device *dev) +{ + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); + int i; + + dev_dbg(tas_priv->dev, "Runtime Suspend\n"); + + mutex_lock(&tas_priv->codec_lock); + + if (tas_priv->playback_started) { + tasdevice_tuning_switch(tas_priv, 1); + tas_priv->playback_started = false; + } + + 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; + } + + regcache_cache_only(tas_priv->regmap, true); + regcache_mark_dirty(tas_priv->regmap); + + mutex_unlock(&tas_priv->codec_lock); + + return 0; +} + +static int tas2781_runtime_resume(struct device *dev) +{ + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); + unsigned long calib_data_sz = + tas_priv->ndev * TASDEVICE_SPEAKER_CALIBRATION_SIZE; + int ret; + + dev_dbg(tas_priv->dev, "Runtime Resume\n"); + + mutex_lock(&tas_priv->codec_lock); + + regcache_cache_only(tas_priv->regmap, false); + ret = regcache_sync(tas_priv->regmap); + if (ret) { + dev_err(tas_priv->dev, + "Failed to restore register cache: %d\n", ret); + goto out; + } + + tasdevice_prmg_load(tas_priv, tas_priv->cur_prog); + + /* If calibrated data occurs error, dsp will still works with default + * calibrated data inside algo. + */ + if (tas_priv->cali_data.total_sz > calib_data_sz) + tas2781_apply_calib(tas_priv); + +out: + mutex_unlock(&tas_priv->codec_lock); + + return ret; +} + +static int tas2781_system_suspend(struct device *dev) +{ + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); + int ret; + + dev_dbg(tas_priv->dev, "System Suspend\n"); + + ret = pm_runtime_force_suspend(dev); + if (ret) + return ret; + + /* Shutdown chip before system suspend */ + regcache_cache_only(tas_priv->regmap, false); + tasdevice_tuning_switch(tas_priv, 1); + regcache_cache_only(tas_priv->regmap, true); + regcache_mark_dirty(tas_priv->regmap); + + /* + * Reset GPIO may be shared, so cannot reset here. + * However beyond this point, amps may be powered down. + */ + return 0; +} + +static int tas2781_system_resume(struct device *dev) +{ + struct tasdevice_priv *tas_priv = dev_get_drvdata(dev); + unsigned long calib_data_sz = + tas_priv->ndev * TASDEVICE_SPEAKER_CALIBRATION_SIZE; + int i, ret; + + dev_dbg(tas_priv->dev, "System Resume\n"); + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + mutex_lock(&tas_priv->codec_lock); + + 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; + } + tas2781_reset(tas_priv); + tasdevice_prmg_load(tas_priv, tas_priv->cur_prog); + + /* If calibrated data occurs error, dsp will still work with default + * calibrated data inside algo. + */ + if (tas_priv->cali_data.total_sz > calib_data_sz) + tas2781_apply_calib(tas_priv); + mutex_unlock(&tas_priv->codec_lock); + + return 0; +} + +static const struct dev_pm_ops tas2781_hda_pm_ops = { + RUNTIME_PM_OPS(tas2781_runtime_suspend, tas2781_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(tas2781_system_suspend, tas2781_system_resume) +}; + +static const struct i2c_device_id tas2781_hda_i2c_id[] = { + { "tas2781-hda", 0 }, + {} +}; + +static const struct acpi_device_id tas2781_acpi_hda_match[] = { + {"TIAS2781", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, tas2781_acpi_hda_match); + +static struct i2c_driver tas2781_hda_i2c_driver = { + .driver = { + .name = "tas2781-hda", + .acpi_match_table = tas2781_acpi_hda_match, + .pm = &tas2781_hda_pm_ops, + }, + .id_table = tas2781_hda_i2c_id, + .probe_new = tas2781_hda_i2c_probe, + .remove = tas2781_hda_i2c_remove, +}; +module_i2c_driver(tas2781_hda_i2c_driver); + +MODULE_DESCRIPTION("TAS2781 HDA Driver"); +MODULE_AUTHOR("Shenghao Ding, TI, <shenghao-ding@ti.com>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_TAS2781_FMWLIB);
Create tas2781 side codec HDA driver for Lenovo Laptops. The quantity of the speakers has been define in ACPI. All of the tas2781s in the laptop will be aggregated as one audio speaker. The code supports realtek codec as the primary codec. Code offers several controls for digtial/analog gain setting during playback, and other for eq params setting in case of different audio profiles, such as music, voice, movie, etc. Signed-off-by: Shenghao Ding <shenghao-ding@ti.com> --- Changes in v3: - oprimize the indentation - fix the ambiguous error message - The check of the given value to kcontrol's put func is in the sound/tas2781-comlib.c - In tasdevice_info_programs, corrected max value - Seperate snd_kcontrol_new tas2781_dsp_controls into two standalone kcontrol. - All the controls set as const static - Add static for page_array & rgno_array - Remove uncessary blank lines - Add descriptions for tas2781_save_calibration - remove global addr handling in the code - checking subid in switch statement in function tas2781_hda_bind - add force firmware load Kcontrol - rename the kcontrol name to be more undertandable - remove Superfluous cast in tasdevice_fw_ready - correct weird line break in function tas2781_acpi_get_i2c_resource - correct Referencing adev after acpi_dev_put() in tas2781_hda_read_acpi - As to checking the given value in tasdevice_set_profile_id, it seems done by the tasdevice_info_profile - replace strcpy with strscpy in tas2781_hda_read_acpi - rewrite the subid judgement - Add tiwai@suse.de into Cc list - remove the cast in tas2781_acpi_get_i2c_resource - remove else in tas2781_acpi_get_i2c_resource - fix the return value in tasdevice_set_profile_id - remove unneeded NL in tasdevice_config_get - Unifiy the comment style - remove ret = 0 in tasdevice_fw_ready - remove ret in tas2781_save_calibration - remove unused ret in tas2781_hda_playback - add force firmware load Kcontrol --- sound/pci/hda/Kconfig | 15 + sound/pci/hda/Makefile | 2 + sound/pci/hda/tas2781_hda_i2c.c | 858 ++++++++++++++++++++++++++++++++ 3 files changed, 875 insertions(+) create mode 100644 sound/pci/hda/tas2781_hda_i2c.c