/* * Calibration support for Cirrus Logic Smart Amplifiers * * Copyright 2017 Cirrus Logic * * Author: David Rhodes * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wmfw.h" #include "wm_adsp.h" enum cirrus_cspl_mboxstate { CSPL_MBOX_STS_RUNNING = 0, CSPL_MBOX_STS_PAUSED = 1, CSPL_MBOX_STS_RDY_FOR_REINIT = 2, }; enum cirrus_cspl_mboxcmd { CSPL_MBOX_CMD_NONE = 0, CSPL_MBOX_CMD_PAUSE = 1, CSPL_MBOX_CMD_RESUME = 2, CSPL_MBOX_CMD_REINIT = 3, CSPL_MBOX_CMD_STOP_PRE_REINIT = 4, CSPL_MBOX_CMD_UNKNOWN_CMD = -1, CSPL_MBOX_CMD_INVALID_SEQUENCE = -2, }; #define CIRRUS_CAL_VERSION "5.01.18" #define CIRRUS_CAL_DIR_NAME "cirrus_cal" #define CIRRUS_CAL_CONFIG_FILENAME_SUFFIX "-dsp1-spk-prot-calib.bin" #define CIRRUS_CAL_PLAYBACK_FILENAME_SUFFIX "-dsp1-spk-prot.bin" #define CIRRUS_CAL_RDC_SAVE_LOCATION "/efs/cirrus/rdc_cal" #define CIRRUS_CAL_TEMP_SAVE_LOCATION "/efs/cirrus/temp_cal" #define CIRRUS_CAL_VSC_SAVE_LOCATION "/efs/cirrus/vsc_cal" #define CIRRUS_CAL_ISC_SAVE_LOCATION "/efs/cirrus/isc_cal" #define CIRRUS_CAL_COMPLETE_DELAY_MS 1250 #define CIRRUS_CAL_RETRIES 2 #define CIRRUS_CAL_AMBIENT_DEFAULT 23 int cirrus_cal_logger_get_variable(struct cirrus_amp *amp, unsigned int id, unsigned int *result) { unsigned int state; int retries = 100; cirrus_amp_write_ctl(amp, "RTLOG_COUNT", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); cirrus_amp_write_ctl(amp, "RTLOG_VARIABLE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, id); cirrus_amp_write_ctl(amp, "RTLOG_COUNT", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 1); cirrus_amp_write_ctl(amp, "RTLOG_STATE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); cirrus_amp_write_ctl(amp, "RTLOG_ENABLE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 1); do { usleep_range(20, 50); cirrus_amp_read_ctl(amp, "RTLOG_STATE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &state); } while (state == 0 && --retries > 0); if (retries == 0) { dev_err(amp_group->cal_dev, "variable read failed\n"); return -1; } cirrus_amp_read_ctl(amp, "RTLOG_DATA", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, result); cirrus_amp_write_ctl(amp, "RTLOG_ENABLE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); return 0; } static unsigned long long cirrus_cal_rdc_to_ohms(unsigned long rdc) { return ((rdc * CIRRUS_CAL_AMP_CONSTANT_NUM) / CIRRUS_CAL_AMP_CONSTANT_DENOM); } static unsigned int cirrus_cal_vpk_to_mv(unsigned int vpk) { return (vpk * CIRRUS_CAL_VFS_MV) >> 19; } static bool cirrus_cal_vsc_in_range(unsigned int vsc) { return ((vsc <= CIRRUS_CAL_VIMON_CAL_VSC_UB) || (vsc >= CIRRUS_CAL_VIMON_CAL_VSC_LB && vsc <= 0x00FFFFFF)); } static bool cirrus_cal_isc_in_range(unsigned int isc) { return ((isc <= CIRRUS_CAL_VIMON_CAL_ISC_UB) || (isc >= CIRRUS_CAL_VIMON_CAL_ISC_LB && isc <= 0x00FFFFFF)); } static int cirrus_cal_load_config(const char *file, struct cirrus_amp *amp) { struct wm_adsp *dsp = snd_soc_component_get_drvdata(amp->component); int ret; dsp->firmwares[dsp->fw].fullname = true; dsp->firmwares[dsp->fw].binfile = file; ret = wm_adsp_load_coeff(dsp); dsp->firmwares[dsp->fw].fullname = false; dsp->firmwares[dsp->fw].binfile = NULL; return ret; } static int cirrus_cal_start(void); static void cirrus_cal_complete_work(struct work_struct *work) { struct cirrus_amp *amp; struct reg_sequence *post_config; struct regmap *regmap; const char *dsp_part_name; char *playback_config_filename; unsigned long long ohms; unsigned int cal_state, mbox_cmd, mbox_sts; int rdc, status, checksum, temp, vsc, isc, timeout = 100, i; int delay = msecs_to_jiffies(CIRRUS_CAL_COMPLETE_DELAY_MS); bool vsc_in_range, isc_in_range; bool cal_retry = false; mutex_lock(&_group->cal_lock); for (i = 0; i < amp_group->num_amps; i++) { amp = &_group->amps[i]; if (amp->calibration_disable) continue; regmap = amp->regmap; dsp_part_name = amp->dsp_part_name; post_config = amp->post_config; mbox_cmd = amp->mbox_cmd; mbox_sts = amp->mbox_sts; playback_config_filename = kzalloc(PAGE_SIZE, GFP_KERNEL); snprintf(playback_config_filename, PAGE_SIZE, "%s%s", dsp_part_name, CIRRUS_CAL_PLAYBACK_FILENAME_SUFFIX); cirrus_amp_read_ctl(amp, "CAL_STATUS", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &status); cirrus_amp_read_ctl(amp, "CAL_R", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &rdc); cirrus_amp_read_ctl(amp, "CAL_AMBIENT", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &temp); cirrus_amp_read_ctl(amp, "CAL_CHECKSUM", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &checksum); ohms = cirrus_cal_rdc_to_ohms((unsigned long)rdc); cirrus_amp_read_ctl(amp, "CSPL_STATE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); if (cal_state == CSPL_STATE_ERROR) { dev_err(amp_group->cal_dev, "Error during ReDC cal, invalidating results\n"); rdc = status = checksum = 0; } if (amp->perform_vimon_cal) { cirrus_amp_read_ctl(amp, "VSC", WMFW_ADSP2_XM, amp->vimon_alg_id, &vsc); cirrus_amp_read_ctl(amp, "ISC", WMFW_ADSP2_XM, amp->vimon_alg_id, &isc); cirrus_amp_read_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, amp->vimon_alg_id, &cal_state); if (cal_state == CIRRUS_CAL_VIMON_STATUS_INVALID || cal_state == 0) { dev_err(amp_group->cal_dev, "Error during VIMON cal, invalidating results\n"); rdc = status = checksum = 0; } vsc_in_range = cirrus_cal_vsc_in_range(vsc); isc_in_range = cirrus_cal_isc_in_range(isc); if (!vsc_in_range) dev_err(amp_group->cal_dev, "VIMON Cal %s (%s): VSC out of range (%x)\n", amp->dsp_part_name, amp->mfd_suffix, vsc); if (!isc_in_range) dev_err(amp_group->cal_dev, "VIMON Cal %s (%s): ISC out of range (%x)\n", amp->dsp_part_name, amp->mfd_suffix, isc); if (!vsc_in_range || !isc_in_range) { dev_err(amp_group->cal_dev, "VIMON cal out of range, invalidating results\n"); rdc = status = checksum = 0; cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, amp->vimon_alg_id, CIRRUS_CAL_VIMON_STATUS_INVALID); if (amp_group->cal_retry < CIRRUS_CAL_RETRIES) { dev_info(amp_group->cal_dev, "Retry Calibration\n"); cal_retry = true; } } } else { vsc = 0; isc = 0; cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, amp->vimon_alg_id, CIRRUS_CAL_VIMON_STATUS_INVALID); } dev_info(amp_group->cal_dev, "Calibration finished: %s (%s)\n", amp->dsp_part_name, amp->mfd_suffix); dev_info(amp_group->cal_dev, "Duration:\t%d ms\n", CIRRUS_CAL_COMPLETE_DELAY_MS); dev_info(amp_group->cal_dev, "Status:\t%d\n", status); if (status == CSPL_STATUS_OUT_OF_RANGE) dev_err(amp_group->cal_dev, "Calibration out of range\n"); if (status == CSPL_STATUS_INCOMPLETE) dev_err(amp_group->cal_dev, "Calibration incomplete\n"); dev_info(amp_group->cal_dev, "R :\t\t%d (%llu.%04llu Ohms)\n", rdc, ohms >> CIRRUS_CAL_RDC_RADIX, (ohms & (((1 << CIRRUS_CAL_RDC_RADIX) - 1))) * 10000 / (1 << CIRRUS_CAL_RDC_RADIX)); dev_info(amp_group->cal_dev, "Checksum:\t%d\n", checksum); dev_info(amp_group->cal_dev, "Ambient:\t%d\n", temp); usleep_range(5000, 5500); /* Send STOP_PRE_REINIT command and poll for response */ regmap_write(regmap, mbox_cmd, CSPL_MBOX_CMD_STOP_PRE_REINIT); timeout = 100; do { dev_info(amp_group->cal_dev, "waiting for REINIT ready...\n"); usleep_range(1000, 1500); regmap_read(regmap, mbox_sts, &cal_state); } while ((cal_state != CSPL_MBOX_STS_RDY_FOR_REINIT) && --timeout > 0); msleep(100); cirrus_cal_load_config(playback_config_filename, amp); cirrus_amp_write_ctl(amp, "CAL_STATUS", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, status); cirrus_amp_write_ctl(amp, "CAL_R", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, rdc); cirrus_amp_write_ctl(amp, "CAL_AMBIENT", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, temp); cirrus_amp_write_ctl(amp, "CAL_CHECKSUM", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, checksum); /* Send REINIT command and poll for response */ regmap_write(regmap, mbox_cmd, CSPL_MBOX_CMD_REINIT); timeout = 100; do { dev_info(amp_group->cal_dev, "waiting for REINIT done...\n"); usleep_range(1000, 1500); regmap_read(regmap, mbox_sts, &cal_state); } while ((cal_state != CSPL_MBOX_STS_RUNNING) && --timeout > 0); msleep(100); cirrus_amp_read_ctl(amp, "CSPL_STATE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); if (cal_state == CSPL_STATE_ERROR) dev_err(amp_group->cal_dev, "Playback config load error\n"); regmap_multi_reg_write(regmap, post_config, amp->num_post_configs); amp->cal.efs_cache_rdc = rdc; amp->cal.efs_cache_vsc = vsc; amp->cal.efs_cache_isc = isc; amp_group->efs_cache_temp = temp; amp->cal.efs_cache_valid = 1; kfree(playback_config_filename); } if (cal_retry == true) { cirrus_cal_start(); queue_delayed_work(system_unbound_wq, &_group->cal_complete_work, delay); amp_group->cal_retry++; } else { amp_group->cal_running = 0; } dev_dbg(amp_group->cal_dev, "Calibration complete\n"); mutex_unlock(&_group->cal_lock); } static void cirrus_cal_v_val_complete(struct cirrus_amp *amps, int num_amps, bool separate) { struct regmap *regmap; struct reg_sequence *post_config; const char *dsp_part_name; char *playback_config_filename; unsigned int mbox_cmd, mbox_sts, cal_state; int timeout = 100, amp; for (amp = 0; amp < num_amps; amp++) { if (amps[amp].v_val_separate && !separate) continue; regmap = amps[amp].regmap; dsp_part_name = amps[amp].dsp_part_name; post_config = amps[amp].post_config; mbox_cmd = amps[amp].mbox_cmd; mbox_sts = amps[amp].mbox_sts; playback_config_filename = kzalloc(PAGE_SIZE, GFP_KERNEL); snprintf(playback_config_filename, PAGE_SIZE, "%s%s", dsp_part_name, CIRRUS_CAL_PLAYBACK_FILENAME_SUFFIX); /* Send STOP_PRE_REINIT command and poll for response */ regmap_write(regmap, mbox_cmd, CSPL_MBOX_CMD_STOP_PRE_REINIT); timeout = 100; do { dev_info(amp_group->cal_dev, "waiting for REINIT ready...\n"); usleep_range(1000, 1500); regmap_read(regmap, mbox_sts, &cal_state); } while ((cal_state != CSPL_MBOX_STS_RDY_FOR_REINIT) && --timeout > 0); msleep(100); cirrus_cal_load_config(playback_config_filename, &_group->amps[amp]); /* Send REINIT command and poll for response */ regmap_write(regmap, mbox_cmd, CSPL_MBOX_CMD_REINIT); timeout = 100; do { dev_info(amp_group->cal_dev, "waiting for REINIT done...\n"); usleep_range(1000, 1500); regmap_read(regmap, mbox_sts, &cal_state); } while ((cal_state != CSPL_MBOX_STS_RUNNING) && --timeout > 0); msleep(100); cirrus_amp_read_ctl(&_group->amps[amp], "CSPL_STATE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); if (cal_state == CSPL_STATE_ERROR) dev_err(amp_group->cal_dev, "Playback config load error\n"); regmap_multi_reg_write(regmap, post_config, amps[amp].num_post_configs); kfree(playback_config_filename); } dev_info(amp_group->cal_dev, "V validation complete\n"); } static int cirrus_cal_get_power_temp(void) { union power_supply_propval value = {0}; struct power_supply *psy; int ret; psy = power_supply_get_by_name("battery"); if (!psy) { dev_warn(amp_group->cal_dev, "failed to get battery, assuming %d\n", CIRRUS_CAL_AMBIENT_DEFAULT); return CIRRUS_CAL_AMBIENT_DEFAULT; } ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &value); if (ret) { dev_warn(amp_group->cal_dev, "failed to get battery temp prop (%d), assuming %d\n", ret, CIRRUS_CAL_AMBIENT_DEFAULT); return CIRRUS_CAL_AMBIENT_DEFAULT; } return DIV_ROUND_CLOSEST(value.intval, 10); } static void cirrus_cal_vimon_cal_start(struct cirrus_amp *amp) { cirrus_amp_write_ctl(amp, "VIMON_CLASS_H_CAL_DELAY", WMFW_ADSP2_XM, amp->vimon_alg_id, CIRRUS_CAL_CLASSH_DELAY_50MS); cirrus_amp_write_ctl(amp, "VIMON_CLASS_D_CAL_DELAY", WMFW_ADSP2_XM, amp->vimon_alg_id, CIRRUS_CAL_CLASSD_DELAY_50MS); cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, amp->vimon_alg_id, 0); cirrus_amp_write_ctl(amp, "HALO_HEARTBEAT", WMFW_ADSP2_XM, amp->halo_alg_id, 0); } static int cirrus_cal_vimon_cal_complete(struct cirrus_amp *amp) { unsigned int vimon_cal, vsc, isc; bool vsc_in_range, isc_in_range; cirrus_amp_read_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, amp->vimon_alg_id, &vimon_cal); cirrus_amp_read_ctl(amp, "VSC", WMFW_ADSP2_XM, amp->vimon_alg_id, &vsc); cirrus_amp_read_ctl(amp, "ISC", WMFW_ADSP2_XM, amp->vimon_alg_id, &isc); dev_info(amp_group->cal_dev, "VIMON Cal results %s (%s), status=%d vsc=%x isc=%x\n", amp->dsp_part_name, amp->mfd_suffix, vimon_cal, vsc, isc); vsc_in_range = cirrus_cal_vsc_in_range(vsc); isc_in_range = cirrus_cal_isc_in_range(isc); if (!vsc_in_range || !isc_in_range) vimon_cal = CIRRUS_CAL_VIMON_STATUS_INVALID; return vimon_cal; } static int cirrus_cal_wait_for_active(struct cirrus_amp *amp) { struct regmap *regmap = amp->regmap; unsigned int global_en; unsigned int halo_state; int timeout = 50; regmap_read(regmap, amp->global_en, &global_en); while ((global_en & amp->global_en_mask) == 0) { usleep_range(1000, 1500); regmap_read(regmap, amp->global_en, &global_en); } do { dev_info(amp_group->cal_dev, "waiting for HALO start...\n"); usleep_range(16000, 16100); cirrus_amp_read_ctl(amp, "HALO_STATE", WMFW_ADSP2_XM, amp->halo_alg_id, &halo_state); timeout--; } while ((halo_state == 0) && timeout > 0); if (timeout == 0) { dev_err(amp_group->cal_dev, "Failed to setup calibration\n"); return -EINVAL; } return 0; } static void cirrus_cal_redc_start(struct cirrus_amp *amp) { struct regmap *regmap = amp->regmap; const char *dsp_part_name = amp->dsp_part_name; char *cal_config_filename; unsigned int halo_state; int timeout = 50; int ambient; cal_config_filename = kzalloc(PAGE_SIZE, GFP_KERNEL); snprintf(cal_config_filename, PAGE_SIZE, "%s%s", dsp_part_name, CIRRUS_CAL_CONFIG_FILENAME_SUFFIX); dev_info(amp_group->cal_dev, "ReDC Calibration load start\n"); /* Send STOP_PRE_REINIT command and poll for response */ regmap_write(regmap, amp->mbox_cmd, CSPL_MBOX_CMD_STOP_PRE_REINIT); timeout = 100; do { dev_info(amp_group->cal_dev, "waiting for REINIT ready...\n"); usleep_range(1000, 1500); regmap_read(regmap, amp->mbox_sts, &halo_state); } while ((halo_state != CSPL_MBOX_STS_RDY_FOR_REINIT) && --timeout > 0); if (timeout == 0) dev_err(amp->component->dev, "REINIT ready not found\n"); msleep(100); dev_dbg(amp_group->cal_dev, "load %s\n", dsp_part_name); cirrus_cal_load_config(cal_config_filename, amp); ambient = cirrus_cal_get_power_temp(); cirrus_amp_write_ctl(amp, "CAL_AMBIENT", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, ambient); /* Send REINIT command and poll for response */ regmap_write(regmap, amp->mbox_cmd, CSPL_MBOX_CMD_REINIT); timeout = 100; do { dev_info(amp_group->cal_dev, "waiting for REINIT done...\n"); usleep_range(1000, 1500); regmap_read(regmap, amp->mbox_sts, &halo_state); } while ((halo_state != CSPL_MBOX_STS_RUNNING) && --timeout > 0); if (timeout == 0) dev_err(amp->component->dev, "REINIT done not found\n"); kfree(cal_config_filename); } int cirrus_cal_apply(const char *mfd_suffix) { struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix); unsigned int temp, rdc, status, checksum, vsc, isc; unsigned int vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_SUCCESS; int ret = 0; if (!amp) return 0; if (amp->cal.efs_cache_valid == 1) { rdc = amp->cal.efs_cache_rdc; vsc = amp->cal.efs_cache_vsc; isc = amp->cal.efs_cache_isc; vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_SUCCESS; temp = amp_group->efs_cache_temp; } else { dev_info(amp_group->cal_dev, "No saved EFS, writing defaults\n"); rdc = amp->default_redc; temp = CIRRUS_CAL_AMBIENT_DEFAULT; vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_INVALID; amp->cal.efs_cache_rdc = rdc; amp_group->efs_cache_temp = temp; } status = 1; checksum = status + rdc; dev_info(amp_group->cal_dev, "Writing calibration to %s (%s)\n", amp->dsp_part_name, mfd_suffix); dev_info(amp_group->cal_dev, "RDC = %d, Temp = %d, Status = %d Checksum = %d\n", rdc, temp, status, checksum); cirrus_amp_write_ctl(amp, "CAL_STATUS", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, status); cirrus_amp_write_ctl(amp, "CAL_R", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, rdc); cirrus_amp_write_ctl(amp, "CAL_AMBIENT", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, temp); cirrus_amp_write_ctl(amp, "CAL_CHECKSUM", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, checksum); if (!amp->perform_vimon_cal) { cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, amp->vimon_alg_id, CIRRUS_CAL_VIMON_STATUS_INVALID); goto skip_vimon_cal; } cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, amp->vimon_alg_id, vimon_cal_status); if (amp->perform_vimon_cal && vimon_cal_status != CIRRUS_CAL_VIMON_STATUS_INVALID) { dev_info(amp_group->cal_dev, "VIMON Cal status=%d vsc=%x isc=%x\n", vimon_cal_status, vsc, isc); cirrus_amp_write_ctl(amp, "VSC", WMFW_ADSP2_XM, amp->vimon_alg_id, vsc); cirrus_amp_write_ctl(amp, "ISC", WMFW_ADSP2_XM, amp->vimon_alg_id, isc); } else { dev_info(amp_group->cal_dev, "VIMON Cal status invalid\n"); } skip_vimon_cal: return ret; } EXPORT_SYMBOL_GPL(cirrus_cal_apply); int cirrus_cal_read_temp(const char *mfd_suffix) { struct cirrus_amp *amp; int reg = 0, ret; unsigned int halo_state; unsigned int global_en; amp = cirrus_get_amp_from_suffix(mfd_suffix); if (!amp) goto err; regmap_read(amp->regmap, amp->global_en, &global_en); if ((global_en & amp->global_en_mask) == 0) goto err; regmap_read(amp->regmap, amp->mbox_sts, &halo_state); if (halo_state != CSPL_MBOX_STS_RUNNING) goto err; if (amp_group->cal_running) goto err; ret = cirrus_cal_logger_get_variable(amp, CIRRUS_CAL_RTLOG_ID_TEMP, ®); if (ret == 0) { if (reg == 0) cirrus_cal_logger_get_variable(amp, CIRRUS_CAL_RTLOG_ID_TEMP, ®); dev_info(amp_group->cal_dev, "Read temp: %d.%04d degrees C\n", reg >> CIRRUS_CAL_RTLOG_RADIX_TEMP, (reg & (((1 << CIRRUS_CAL_RTLOG_RADIX_TEMP) - 1))) * 10000 / (1 << CIRRUS_CAL_RTLOG_RADIX_TEMP)); return (reg >> CIRRUS_CAL_RTLOG_RADIX_TEMP); } err: return -1; } EXPORT_SYMBOL_GPL(cirrus_cal_read_temp); int cirrus_cal_set_surface_temp(const char *suffix, int temperature) { unsigned int global_en; struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); if (!amp) return -EINVAL; regmap_read(amp->regmap, amp->global_en, &global_en); if ((global_en & amp->global_en_mask) == 0) return -EINVAL; dev_info(amp->component->dev, "Set surface temp: %d degrees\n", temperature); cirrus_amp_write_ctl(amp, "CSPL_SURFACE_TEMP", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, temperature); return 0; } EXPORT_SYMBOL_GPL(cirrus_cal_set_surface_temp); static int cirrus_cal_start(void) { int redc_cal_start_retries, vimon_cal_retries = 0; bool vimon_calibration_failed = false; unsigned int cal_state; int amp; struct reg_sequence *config; struct regmap *regmap; int ret; for (amp = 0; amp < amp_group->num_amps; amp++) { if (amp_group->amps[amp].calibration_disable) continue; regmap = amp_group->amps[amp].regmap; cirrus_amp_write_ctl(&_group->amps[amp], "CAL_STATUS", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); cirrus_amp_write_ctl(&_group->amps[amp], "CAL_R", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); cirrus_amp_write_ctl(&_group->amps[amp], "CAL_AMBIENT", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); cirrus_amp_write_ctl(&_group->amps[amp], "CAL_CHECKSUM", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); if (amp_group->amps[amp].perform_vimon_cal) { cirrus_amp_write_ctl(&_group->amps[amp], "VSC", WMFW_ADSP2_XM, amp_group->amps[amp].vimon_alg_id, 0); cirrus_amp_write_ctl(&_group->amps[amp], "ISC", WMFW_ADSP2_XM, amp_group->amps[amp].vimon_alg_id, 0); } ret = cirrus_cal_wait_for_active(&_group->amps[amp]); if (ret < 0) { dev_err(amp_group->cal_dev, "Could not start amp%s (%d)\n", amp_group->amps[amp].mfd_suffix, ret); return -ETIMEDOUT; } } do { vimon_calibration_failed = false; for (amp = 0; amp < amp_group->num_amps; amp++) { if (amp_group->amps[amp].calibration_disable) continue; regmap = amp_group->amps[amp].regmap; config = amp_group->amps[amp].pre_config; regmap_multi_reg_write(regmap, config, amp_group->amps[amp].num_pre_configs); if (amp_group->amps[amp].perform_vimon_cal) cirrus_cal_vimon_cal_start(&_group->amps[amp]); } msleep(112); for (amp = 0; amp < amp_group->num_amps; amp++) { if (amp_group->amps[amp].calibration_disable) continue; if (amp_group->amps[amp].perform_vimon_cal) { ret = cirrus_cal_vimon_cal_complete( &_group->amps[amp]); if (ret != CIRRUS_CAL_VIMON_STATUS_SUCCESS) { vimon_calibration_failed = true; dev_info(amp_group->cal_dev, "VIMON Calibration Error %s (%s)\n", amp_group->amps[amp].dsp_part_name, amp_group->amps[amp].mfd_suffix); } } } vimon_cal_retries--; } while (vimon_cal_retries >= 0 && vimon_calibration_failed); for (amp = 0; amp < amp_group->num_amps; amp++) { if (amp_group->amps[amp].calibration_disable) continue; regmap = amp_group->amps[amp].regmap; cirrus_cal_redc_start(&_group->amps[amp]); cirrus_amp_read_ctl(&_group->amps[amp], "CSPL_STATE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); redc_cal_start_retries = 5; while (cal_state == CSPL_STATE_ERROR && redc_cal_start_retries > 0) { if (cal_state == CSPL_STATE_ERROR) dev_err(amp_group->cal_dev, "Calibration load error\n"); cirrus_cal_redc_start(&_group->amps[amp]); cirrus_amp_read_ctl(&_group->amps[amp], "CSPL_STATE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); redc_cal_start_retries--; } if (redc_cal_start_retries == 0) { config = amp_group->amps[amp].post_config; dev_err(amp_group->cal_dev, "Calibration setup fail amp%s (%d)\n", amp_group->amps[amp].mfd_suffix, ret); regmap_multi_reg_write(regmap, config, amp_group->amps[amp].num_post_configs); return -ETIMEDOUT; } } return 0; } /***** SYSFS Interfaces *****/ static ssize_t cirrus_cal_version_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, CIRRUS_CAL_VERSION "\n"); } static ssize_t cirrus_cal_version_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { return 0; } static ssize_t cirrus_cal_status_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%s\n", amp_group->cal_running ? "Enabled" : "Disabled"); } static ssize_t cirrus_cal_status_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int ret = 0, prepare; if (amp_group->cal_running) { dev_err(amp_group->cal_dev, "cirrus_cal measurement in progress\n"); return size; } mutex_lock(&_group->cal_lock); ret = kstrtos32(buf, 10, &prepare); if (ret != 0 || prepare != 1) goto err; amp_group->cal_running = true; amp_group->cal_retry = 0; cirrus_cal_start(); dev_dbg(amp_group->cal_dev, "Calibration prepare complete\n"); queue_delayed_work(system_unbound_wq, &_group->cal_complete_work, msecs_to_jiffies(CIRRUS_CAL_COMPLETE_DELAY_MS)); err: mutex_unlock(&_group->cal_lock); if (ret < 0) amp_group->cal_running = false; return size; } static ssize_t cirrus_cal_v_status_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%s\n", amp_group->cal_running ? "Enabled" : "Disabled"); } static ssize_t cirrus_cal_v_status_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct regmap *regmap; struct reg_sequence *config; unsigned int vmax[CIRRUS_MAX_AMPS]; unsigned int vmin[CIRRUS_MAX_AMPS]; unsigned int cal_state; int ret = 0, i, j, reg, prepare, retries, num_amps, count; const char *suffix; struct cirrus_amp *amps; bool separate = false; if (amp_group->cal_running) { dev_err(amp_group->cal_dev, "cirrus_cal measurement in progress\n"); return size; } mutex_lock(&_group->cal_lock); ret = kstrtos32(buf, 10, &prepare); if (ret != 0 || prepare != 1) goto err; amp_group->cal_running = true; if (strlen(attr->attr.name) > strlen("v_status")) { suffix = &(attr->attr.name[strlen("v_status")]); amps = cirrus_get_amp_from_suffix(suffix); if (amps) { dev_info(dev, "V-validation for amp: %s (%s)\n", amps->dsp_part_name, suffix); num_amps = 1; separate = true; } else { mutex_unlock(&_group->cal_lock); return size; } } else { num_amps = amp_group->num_amps; amps = amp_group->amps; separate = false; } for (i = 0; i < amp_group->num_amps; i++) { if (amps[i].v_val_separate && !separate) continue; regmap = amps[i].regmap; config = amps[i].pre_config; vmax[i] = 0; vmin[i] = INT_MAX; ret = cirrus_cal_wait_for_active(&s[i]); if (ret < 0) { dev_err(amp_group->cal_dev, "Could not start amp%s\n", amps[i].mfd_suffix); goto err; } regmap_multi_reg_write(regmap, config, amps[i].num_pre_configs); cirrus_cal_redc_start(&s[i]); cirrus_amp_read_ctl(&s[i], "CSPL_STATE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); retries = 5; while (cal_state == CSPL_STATE_ERROR && retries > 0) { if (cal_state == CSPL_STATE_ERROR) dev_err(amp_group->cal_dev, "Calibration load error\n"); cirrus_cal_redc_start(&s[i]); cirrus_amp_read_ctl(&s[i], "CSPL_STATE", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); retries--; } if (retries == 0) { config = amps[i].post_config; dev_err(amp_group->cal_dev, "Calibration setup fail @ %d\n", i); regmap_multi_reg_write(regmap, config, amps[i].num_post_configs); goto err; } } dev_info(amp_group->cal_dev, "V validation prepare complete\n"); for (i = 0; i < 1000; i++) { count = 0; for (j = 0; j < num_amps; j++) { if (amps[j].v_val_separate && !separate) continue; regmap = amps[j].regmap; cirrus_cal_logger_get_variable(&s[j], amps[j].cal_vpk_id, ®); if (reg > vmax[j]) vmax[j] = reg; if (reg < vmin[j]) vmin[j] = reg; cirrus_amp_read_ctl(&_group->amps[j], "CAL_STATUS", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, ®); if (reg != 0 && reg != CSPL_STATUS_INCOMPLETE) count++; } if (count == num_amps) { dev_info(amp_group->cal_dev, "V Validation complete (%d)\n", i); break; } } for (i = 0; i < num_amps; i++) { if (amps[i].v_val_separate && !separate) continue; dev_info(amp_group->cal_dev, "V Validation results for amp%s\n", amps[i].mfd_suffix); dev_dbg(amp_group->cal_dev, "V Max: 0x%x\n", vmax[i]); vmax[i] = cirrus_cal_vpk_to_mv(vmax[i]); dev_info(amp_group->cal_dev, "V Max: %d mV\n", vmax[i]); dev_dbg(amp_group->cal_dev, "V Min: 0x%x\n", vmin[i]); vmin[i] = cirrus_cal_vpk_to_mv(vmin[i]); dev_info(amp_group->cal_dev, "V Min: %d mV\n", vmin[i]); if (vmax[i] < CIRRUS_CAL_V_VAL_UB_MV && vmax[i] > CIRRUS_CAL_V_VAL_LB_MV) { amps[i].cal.v_validation = 1; dev_info(amp_group->cal_dev, "V validation success\n"); } else { amps[i].cal.v_validation = 0xCC; dev_err(amp_group->cal_dev, "V validation failed\n"); } } cirrus_cal_v_val_complete(amps, num_amps, separate); err: amp_group->cal_running = false; mutex_unlock(&_group->cal_lock); return size; } #ifdef CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS static ssize_t cirrus_cal_reinit_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "\n"); } static ssize_t cirrus_cal_reinit_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int reinit, i; int ret = kstrtos32(buf, 10, &reinit); if (amp_group->cal_running) { dev_err(amp_group->cal_dev, "cirrus_cal measurement in progress\n"); return size; } if (ret == 0 && reinit == 1) { mutex_lock(&_group->cal_lock); for (i = 0; i < amp_group->num_amps; i++) { if (amp_group->amps[i].amp_reinit != NULL) amp_group->amps[i].amp_reinit( amp_group->amps[i].component); } mutex_unlock(&_group->cal_lock); } return size; } #endif /* CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS*/ static ssize_t cirrus_cal_vval_show(struct device *dev, struct device_attribute *attr, char *buf) { const char *suffix = &(attr->attr.name[strlen("v_validation")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); dev_info(dev, "%s\n", __func__); return sprintf(buf, "%d", amp->cal.v_validation); } static ssize_t cirrus_cal_vval_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { dev_info(dev, "%s\n", __func__); return 0; } static ssize_t cirrus_cal_rdc_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned int rdc; const char *suffix = &(attr->attr.name[strlen("rdc")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); if (amp) { rdc = amp->cal.efs_cache_rdc; return sprintf(buf, "%d", rdc); } else return 0; } static ssize_t cirrus_cal_rdc_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int rdc, ret; const char *suffix = &(attr->attr.name[strlen("rdc")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); bool vimon_valid; ret = kstrtos32(buf, 10, &rdc); if (ret == 0 && amp) { if (rdc < 0) { amp->cal.efs_cache_vsc = 0; amp->cal.efs_cache_isc = 0; amp->cal.efs_cache_rdc = 0; amp->cal.efs_cache_valid = 0; return size; } amp->cal.efs_cache_rdc = rdc; dev_info(dev, "EFS Cache RDC set: 0x%x\n", rdc); vimon_valid = (!amp->perform_vimon_cal) || (amp->cal.efs_cache_vsc && amp->cal.efs_cache_isc); if (amp->cal.efs_cache_rdc && amp_group->efs_cache_temp && vimon_valid) amp->cal.efs_cache_valid = 1; } return size; } static ssize_t cirrus_cal_vsc_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned int vsc; const char *suffix = &(attr->attr.name[strlen("vsc")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); if (amp) { vsc = amp->cal.efs_cache_vsc; return sprintf(buf, "%d", vsc); } else return 0; } static ssize_t cirrus_cal_vsc_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int vsc, ret; const char *suffix = &(attr->attr.name[strlen("vsc")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); bool vimon_valid; ret = kstrtos32(buf, 10, &vsc); if (ret == 0 && amp) { if (vsc < 0) { amp->cal.efs_cache_vsc = 0; amp->cal.efs_cache_isc = 0; amp->cal.efs_cache_rdc = 0; amp->cal.efs_cache_valid = 0; return size; } amp->cal.efs_cache_vsc = vsc; dev_info(dev, "EFS Cache VSC set: 0x%x\n", vsc); vimon_valid = (!amp->perform_vimon_cal) || (amp->cal.efs_cache_vsc && amp->cal.efs_cache_isc); if (amp->cal.efs_cache_rdc && amp_group->efs_cache_temp && vimon_valid) amp->cal.efs_cache_valid = 1; } return size; } static ssize_t cirrus_cal_isc_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned int isc; const char *suffix = &(attr->attr.name[strlen("isc")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); if (amp) { isc = amp->cal.efs_cache_isc; return sprintf(buf, "%d", isc); } else return 0; } static ssize_t cirrus_cal_isc_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int isc, ret; const char *suffix = &(attr->attr.name[strlen("isc")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); bool vimon_valid; ret = kstrtos32(buf, 10, &isc); if (ret == 0 && amp) { if (isc < 0) { amp->cal.efs_cache_vsc = 0; amp->cal.efs_cache_isc = 0; amp->cal.efs_cache_rdc = 0; amp->cal.efs_cache_valid = 0; return size; } amp->cal.efs_cache_isc = isc; dev_info(dev, "EFS Cache ISC set: 0x%x\n", isc); vimon_valid = (!amp->perform_vimon_cal) || (amp->cal.efs_cache_vsc && amp->cal.efs_cache_isc); if (amp->cal.efs_cache_rdc && amp_group->efs_cache_temp && vimon_valid) amp->cal.efs_cache_valid = 1; } return size; } static ssize_t cirrus_cal_temp_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned int temp; const char *suffix = &(attr->attr.name[strlen("temp")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); if (amp) { temp = amp_group->efs_cache_temp; return sprintf(buf, "%d", temp); } else return 0; } static ssize_t cirrus_cal_temp_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int temp, ret; const char *suffix = &(attr->attr.name[strlen("temp")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); bool vimon_valid; ret = kstrtos32(buf, 10, &temp); if (ret == 0 && amp) { amp_group->efs_cache_temp = temp; dev_info(dev, "EFS Cache temp set: %d\n", temp); vimon_valid = (!amp->perform_vimon_cal) || (amp->cal.efs_cache_vsc && amp->cal.efs_cache_isc); if (amp->cal.efs_cache_rdc && amp_group->efs_cache_temp && vimon_valid) amp->cal.efs_cache_valid = 1; } return size; } static ssize_t cirrus_cal_checksum_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned int checksum; const char *suffix = &(attr->attr.name[strlen("checksum")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); if (amp) { cirrus_amp_read_ctl(amp, "CAL_CHECKSUM", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &checksum); return sprintf(buf, "%d", checksum); } else return 0; } static ssize_t cirrus_cal_checksum_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int checksum, ret; const char *suffix = &(attr->attr.name[strlen("checksum")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); ret = kstrtos32(buf, 10, &checksum); if (ret == 0 && amp) cirrus_amp_write_ctl(amp, "CAL_CHECKSUM", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, checksum); return size; } static ssize_t cirrus_cal_set_status_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned int set_status; const char *suffix = &(attr->attr.name[strlen("set_status")]); struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); if (amp) { cirrus_amp_read_ctl(amp, "CAL_SET_STATUS", WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &set_status); return sprintf(buf, "%d", set_status); } else return 0; } static ssize_t cirrus_cal_set_status_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { return 0; } static DEVICE_ATTR(version, 0444, cirrus_cal_version_show, cirrus_cal_version_store); static DEVICE_ATTR(status, 0664, cirrus_cal_status_show, cirrus_cal_status_store); static DEVICE_ATTR(v_status, 0664, cirrus_cal_v_status_show, cirrus_cal_v_status_store); #ifdef CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS static DEVICE_ATTR(reinit, 0664, cirrus_cal_reinit_show, cirrus_cal_reinit_store); #endif /* CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS */ static struct device_attribute v_val_attribute = { .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, .show = cirrus_cal_v_status_show, .store = cirrus_cal_v_status_store, }; static struct device_attribute generic_amp_attrs[CIRRUS_CAL_NUM_ATTRS_AMP] = { { .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, .show = cirrus_cal_vval_show, .store = cirrus_cal_vval_store, }, { .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, .show = cirrus_cal_rdc_show, .store = cirrus_cal_rdc_store, }, { .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, .show = cirrus_cal_vsc_show, .store = cirrus_cal_vsc_store, }, { .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, .show = cirrus_cal_isc_show, .store = cirrus_cal_isc_store, }, { .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, .show = cirrus_cal_temp_show, .store = cirrus_cal_temp_store, }, { .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, .show = cirrus_cal_checksum_show, .store = cirrus_cal_checksum_store, }, { .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, .show = cirrus_cal_set_status_show, .store = cirrus_cal_set_status_store, }, }; static const char *generic_amp_attr_names[CIRRUS_CAL_NUM_ATTRS_AMP] = { "v_validation", "rdc", "vsc", "isc", "temp", "checksum", "set_status" }; static struct attribute *cirrus_cal_attr_base[] = { &dev_attr_version.attr, &dev_attr_status.attr, &dev_attr_v_status.attr, #ifdef CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS &dev_attr_reinit.attr, #endif /* CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS */ NULL, }; /* Kernel does not allow attributes to be dynamically allocated */ static struct attribute_group cirrus_cal_attr_grp; static struct device_attribute amp_attrs_prealloc[CIRRUS_MAX_AMPS][CIRRUS_CAL_NUM_ATTRS_AMP]; static char attr_names_prealloc[CIRRUS_MAX_AMPS][CIRRUS_CAL_NUM_ATTRS_AMP][20]; static char v_val_attr_names_prealloc[CIRRUS_MAX_AMPS][20]; static struct device_attribute v_val_attrs_prealloc[CIRRUS_MAX_AMPS]; struct device_attribute *cirrus_cal_create_amp_attrs(const char *mfd_suffix, int index) { struct device_attribute *amp_attrs_new; int i, suffix_len = strlen(mfd_suffix); if (index >= CIRRUS_MAX_AMPS) return NULL; amp_attrs_new = &(amp_attrs_prealloc[index][0]); memcpy(amp_attrs_new, &generic_amp_attrs, sizeof(struct device_attribute) * CIRRUS_CAL_NUM_ATTRS_AMP); for (i = 0; i < CIRRUS_CAL_NUM_ATTRS_AMP; i++) { amp_attrs_new[i].attr.name = attr_names_prealloc[index][i]; snprintf((char *)amp_attrs_new[i].attr.name, strlen(generic_amp_attr_names[i]) + suffix_len + 1, "%s%s", generic_amp_attr_names[i], mfd_suffix); } return amp_attrs_new; } int cirrus_cal_init(void) { struct device_attribute *new_attrs; int ret = 0, i, j, num_amps, v_val_num_attrs = 0; if (!amp_group) { pr_err("%s: Empty amp group\n", __func__); return -ENODATA; } amp_group->cal_dev = device_create(cirrus_amp_class, NULL, 1, NULL, CIRRUS_CAL_DIR_NAME); if (IS_ERR(amp_group->cal_dev)) { ret = PTR_ERR(amp_group->cal_dev); pr_err("%s: Failed to create CAL device (%d)\n", __func__, ret); return ret; } dev_set_drvdata(amp_group->cal_dev, amp_group); num_amps = amp_group->num_amps; for (i = 0; i < num_amps; i++) { if (amp_group->amps[i].v_val_separate) v_val_num_attrs++; } cirrus_cal_attr_grp.attrs = kzalloc(sizeof(struct attribute *) * (CIRRUS_CAL_NUM_ATTRS_AMP * num_amps + v_val_num_attrs + CIRRUS_CAL_NUM_ATTRS_BASE + 1), GFP_KERNEL); for (i = 0; i < num_amps; i++) { new_attrs = cirrus_cal_create_amp_attrs( amp_group->amps[i].mfd_suffix, i); for (j = 0; j < CIRRUS_CAL_NUM_ATTRS_AMP; j++) { dev_dbg(amp_group->cal_dev, "New attribute: %s\n", new_attrs[j].attr.name); cirrus_cal_attr_grp.attrs[i * CIRRUS_CAL_NUM_ATTRS_AMP + j] = &new_attrs[j].attr; } } for (i = j = 0; i < num_amps; i++) { if (amp_group->amps[i].v_val_separate) { memcpy(&v_val_attrs_prealloc[j], &v_val_attribute, sizeof(struct device_attribute)); v_val_attrs_prealloc[j].attr.name = v_val_attr_names_prealloc[j]; snprintf((char *)v_val_attrs_prealloc[j].attr.name, strlen("v_status") + strlen(amp_group->amps[i].mfd_suffix) + 1, "v_status%s", amp_group->amps[i].mfd_suffix); dev_info(amp_group->cal_dev, "New attribute: %s\n", v_val_attrs_prealloc[j].attr.name); cirrus_cal_attr_grp.attrs[num_amps * CIRRUS_CAL_NUM_ATTRS_AMP + j] = &v_val_attrs_prealloc[j].attr; j++; } } memcpy(&cirrus_cal_attr_grp.attrs[num_amps * CIRRUS_CAL_NUM_ATTRS_AMP + v_val_num_attrs], cirrus_cal_attr_base, sizeof(struct attribute *) * CIRRUS_CAL_NUM_ATTRS_BASE); cirrus_cal_attr_grp.attrs[num_amps * CIRRUS_CAL_NUM_ATTRS_AMP + CIRRUS_CAL_NUM_ATTRS_BASE + v_val_num_attrs] = NULL; ret = sysfs_create_group(&_group->cal_dev->kobj, &cirrus_cal_attr_grp); if (ret) { dev_err(amp_group->cal_dev, "Failed to create sysfs group\n"); device_del(amp_group->bd_dev); return ret; } mutex_init(&_group->cal_lock); INIT_DELAYED_WORK(&_group->cal_complete_work, cirrus_cal_complete_work); return ret; } void cirrus_cal_exit(void) { flush_work(&_group->cal_complete_work.work); mutex_destroy(&_group->cal_lock); kfree(cirrus_cal_attr_grp.attrs); device_del(amp_group->bd_dev); }