/* * Calibration support for Cirrus Logic CS35L41 codec * * 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 #include #include #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 CS35L41_CAL_COMPLETE_DELAY_MS 1250 #define CS34L40_CAL_AMBIENT_DEFAULT 23 #define CS34L40_CAL_RDC_DEFAULT 8580 struct cirrus_cal_t { struct class *cal_class; struct device *dev; struct cirrus_mfd_amp *amps; int num_amps; bool cal_running; struct mutex lock; struct delayed_work cal_complete_work; unsigned int efs_cache_temp; int efs_cache_read[CIRRUS_MAX_AMPS]; unsigned int efs_cache_rdc[CIRRUS_MAX_AMPS]; unsigned int v_validation[CIRRUS_MAX_AMPS]; unsigned int dsp_input1_cache[CIRRUS_MAX_AMPS]; unsigned int dsp_input2_cache[CIRRUS_MAX_AMPS]; #ifdef CS35L41_FACTORY_RECOVERY_SYSFS struct snd_soc_codec *codecs[CIRRUS_MAX_AMPS]; #endif /* CS35L41_FACTORY_RECOVERY_SYSFS */ }; static struct cirrus_cal_t *cirrus_cal; static struct attribute_group cirrus_cal_attr_grp; struct cs35l41_dsp_buf { struct list_head list; void *buf; }; struct cirrus_mfd_amp *cirrus_cal_get_amp_from_suffix(const char *suffix) { int i; struct cirrus_mfd_amp *ret = NULL; if (cirrus_cal == NULL || cirrus_cal->amps == NULL) return NULL; dev_dbg(cirrus_cal->dev, "%s: suffix = %s\n", __func__, suffix); for (i = 0; i < cirrus_cal->num_amps; i++) { dev_dbg(cirrus_cal->dev, "comparing %s & %s\n", cirrus_cal->amps[i].mfd_suffix, suffix); if (strcmp(cirrus_cal->amps[i].mfd_suffix, suffix) == 0) ret = &cirrus_cal->amps[i]; } return ret; } #ifdef CS35L41_FACTORY_RECOVERY_SYSFS int cirrus_cal_codec_add(struct snd_soc_codec *codec, const char *mfd_suffix) { struct cirrus_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(mfd_suffix); if (cirrus_cal){ if (amp) { dev_info(cirrus_cal->dev, "Codec added, suffix: %s\n", mfd_suffix); cirrus_cal->codecs[amp->index] = codec; } else { dev_err(cirrus_cal->dev, "No amp with suffix %s registered\n", mfd_suffix); } } else { return -EINVAL; } return 0; } #endif /* CS35L41_FACTORY_RECOVERY_SYSFS */ int cirrus_cal_amp_add(struct regmap *regmap_new, const char *mfd_suffix, const char *dsp_part_name) { struct cirrus_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(mfd_suffix); if (cirrus_cal){ if (amp) { dev_info(cirrus_cal->dev, "Amp added, suffix: %s dsp_part_name: %s\n", mfd_suffix, dsp_part_name); amp->regmap = regmap_new; amp->dsp_part_name = dsp_part_name; } else { dev_err(cirrus_cal->dev, "No amp with suffix %s registered\n", mfd_suffix); } } else { return -EINVAL; } return 0; } static struct cs35l41_dsp_buf *cs35l41_dsp_buf_alloc(const void *src, size_t len, struct list_head *list) { struct cs35l41_dsp_buf *buf = kzalloc(sizeof(*buf), GFP_KERNEL); if (buf == NULL) return NULL; buf->buf = vmalloc(len); if (!buf->buf) { kfree(buf); return NULL; } memcpy(buf->buf, src, len); if (list) list_add_tail(&buf->list, list); return buf; } static void cs35l41_dsp_buf_free(struct list_head *list) { while (!list_empty(list)) { struct cs35l41_dsp_buf *buf = list_first_entry(list, struct cs35l41_dsp_buf, list); list_del(&buf->list); vfree(buf->buf); kfree(buf); } } static unsigned long long int cs35l41_rdc_to_ohms(unsigned long int rdc) { return ((rdc * CS35L41_CAL_AMP_CONSTANT_NUM) / CS35L41_CAL_AMP_CONSTANT_DENOM); } static unsigned int cirrus_cal_vpk_to_mv(unsigned int vpk) { return (vpk * CIRRUS_CAL_VFS_MV) >> 19; } static unsigned int cirrus_cal_ipk_to_ma(unsigned int ipk) { return (ipk * CIRRUS_CAL_IFS_MA) >> 19; } static void cirrus_cal_unmute_dsp_inputs(struct regmap *regmap, int cache_index) { regmap_write(regmap, CS35L41_DSP1_RX1_SRC, cirrus_cal->dsp_input1_cache[cache_index]); regmap_write(regmap, CS35L41_DSP1_RX2_SRC, cirrus_cal->dsp_input2_cache[cache_index]); } static void cirrus_cal_mute_dsp_inputs(struct regmap *regmap, int cache_index) { unsigned int src1, src2; regmap_read(regmap, CS35L41_DSP1_RX1_SRC, &src1); if (src1) cirrus_cal->dsp_input1_cache[cache_index] = src1; regmap_read(regmap, CS35L41_DSP1_RX2_SRC, &src2); if (src2) cirrus_cal->dsp_input2_cache[cache_index] = src2; regmap_write(regmap, CS35L41_DSP1_RX1_SRC, 0); regmap_write(regmap, CS35L41_DSP1_RX2_SRC, 0); } static int cs35l41_load_config(const char *file, struct regmap *regmap) { LIST_HEAD(buf_list); struct wmfw_coeff_hdr *hdr; struct wmfw_coeff_item *blk; const struct firmware *firmware; const char *region_name; int ret, pos, blocks, type, offset, reg; struct cs35l41_dsp_buf *buf; ret = request_firmware(&firmware, file, cirrus_cal->dev); if (ret != 0) { dev_err(cirrus_cal->dev, "Failed to request '%s'\n", file); ret = 0; goto out; } ret = -EINVAL; if (sizeof(*hdr) >= firmware->size) { dev_err(cirrus_cal->dev, "%s: file too short, %zu bytes\n", file, firmware->size); goto out_fw; } hdr = (void *)&firmware->data[0]; if (memcmp(hdr->magic, "WMDR", 4) != 0) { dev_err(cirrus_cal->dev, "%s: invalid magic\n", file); goto out_fw; } switch (be32_to_cpu(hdr->rev) & 0xff) { case 1: break; default: dev_err(cirrus_cal->dev, "%s: Unsupported coefficient file format %d\n", file, be32_to_cpu(hdr->rev) & 0xff); ret = -EINVAL; goto out_fw; } dev_dbg(cirrus_cal->dev, "%s: v%d.%d.%d\n", file, (le32_to_cpu(hdr->ver) >> 16) & 0xff, (le32_to_cpu(hdr->ver) >> 8) & 0xff, le32_to_cpu(hdr->ver) & 0xff); pos = le32_to_cpu(hdr->len); blocks = 0; while (pos < firmware->size && pos - firmware->size > sizeof(*blk)) { blk = (void *)(&firmware->data[pos]); type = le16_to_cpu(blk->type); offset = le16_to_cpu(blk->offset); dev_dbg(cirrus_cal->dev, "%s.%d: %x v%d.%d.%d\n", file, blocks, le32_to_cpu(blk->id), (le32_to_cpu(blk->ver) >> 16) & 0xff, (le32_to_cpu(blk->ver) >> 8) & 0xff, le32_to_cpu(blk->ver) & 0xff); dev_dbg(cirrus_cal->dev, "%s.%d: %d bytes at 0x%x in %x\n", file, blocks, le32_to_cpu(blk->len), offset, type); reg = 0; region_name = "Unknown"; switch (type) { case WMFW_ADSP2_YM: dev_dbg(cirrus_cal->dev, "%s.%d: %d bytes in %x for %x\n", file, blocks, le32_to_cpu(blk->len), type, le32_to_cpu(blk->id)); if (le32_to_cpu(blk->id) == 0xcd) { reg = CS35L41_YM_CONFIG_ADDR; reg += offset - 0x8; } break; case WMFW_HALO_YM_PACKED: dev_dbg(cirrus_cal->dev, "%s.%d: %d bytes in %x for %x\n", file, blocks, le32_to_cpu(blk->len), type, le32_to_cpu(blk->id)); if (le32_to_cpu(blk->id) == 0xcd) { /* config addr packed + 1 */ /* config size (config[0]) is not at 24bit packed boundary */ /* so that fist word gets written by itself to unpacked mem */ /* then the rest of it starts here */ /* offset = 3 (groups of 4 24bit words) * 3 (packed words) * 4 bytes */ reg = CS35L41_DSP1_YMEM_PACK_0 + 3 * 4 * 3; } break; default: dev_dbg(cirrus_cal->dev, "%s.%d: region type %x at %d\n", file, blocks, type, pos); break; } if (reg) { if ((pos + le32_to_cpu(blk->len) + sizeof(*blk)) > firmware->size) { dev_err(cirrus_cal->dev, "%s.%d: %s region len %d bytes exceeds file length %zu\n", file, blocks, region_name, le32_to_cpu(blk->len), firmware->size); ret = -EINVAL; goto out_fw; } buf = cs35l41_dsp_buf_alloc(blk->data, le32_to_cpu(blk->len), &buf_list); if (!buf) { dev_err(cirrus_cal->dev, "Out of memory\n"); ret = -ENOMEM; goto out_fw; } dev_dbg(cirrus_cal->dev, "%s.%d: Writing %d bytes at %x\n", file, blocks, le32_to_cpu(blk->len), reg); ret = regmap_raw_write_async(regmap, reg, buf->buf, le32_to_cpu(blk->len)); if (ret != 0) { dev_err(cirrus_cal->dev, "%s.%d: Failed to write to %x in %s: %d\n", file, blocks, reg, region_name, ret); } } pos += (le32_to_cpu(blk->len) + sizeof(*blk) + 3) & ~0x03; blocks++; } ret = regmap_async_complete(regmap); if (ret != 0) dev_err(cirrus_cal->dev, "Failed to complete async write: %d\n", ret); if (pos > firmware->size) dev_err(cirrus_cal->dev, "%s.%d: %zu bytes at end of file\n", file, blocks, pos - firmware->size); dev_info(cirrus_cal->dev, "%s load complete\n", file); out_fw: regmap_async_complete(regmap); release_firmware(firmware); cs35l41_dsp_buf_free(&buf_list); out: return ret; } static void cirrus_cal_complete_work(struct work_struct *work) { int rdc, status, checksum, temp; unsigned long long int ohms; unsigned int cal_state; char *playback_config_filename; int timeout = 100, amp, index; struct regmap *regmap; const char *dsp_part_name; mutex_lock(&cirrus_cal->lock); for (amp = 0; amp < cirrus_cal->num_amps; amp++) { regmap = cirrus_cal->amps[amp].regmap; dsp_part_name = cirrus_cal->amps[amp].dsp_part_name; index = cirrus_cal->amps[amp].index; playback_config_filename = kzalloc(PAGE_SIZE, GFP_KERNEL); snprintf(playback_config_filename, PAGE_SIZE, "%s%s", dsp_part_name, CIRRUS_CAL_PLAYBACK_FILENAME_SUFFIX); regmap_read(regmap, CS35L41_CAL_STATUS, &status); regmap_read(regmap, CS35L41_CAL_RDC, &rdc); regmap_read(regmap, CS35L41_CAL_AMBIENT, &temp); regmap_read(regmap, CS35L41_CAL_CHECKSUM, &checksum); ohms = cs35l41_rdc_to_ohms((unsigned long int)rdc); regmap_read(regmap, CS35L41_CSPL_STATE, &cal_state); if (cal_state == CS35L41_CSPL_STATE_ERROR) { dev_err(cirrus_cal->dev, "Error during calibration, invalidating results\n"); rdc = status = checksum = 0; } dev_info(cirrus_cal->dev, "Calibration finished: cs35l41%s\n", cirrus_cal->amps[amp].mfd_suffix); dev_info(cirrus_cal->dev, "Duration:\t%d ms\n", CS35L41_CAL_COMPLETE_DELAY_MS); dev_info(cirrus_cal->dev, "Status:\t%d\n", status); if (status == CS35L41_CSPL_STATUS_OUT_OF_RANGE) dev_err(cirrus_cal->dev, "Calibration out of range\n"); if (status == CS35L41_CSPL_STATUS_INCOMPLETE) dev_err(cirrus_cal->dev, "Calibration incomplete\n"); dev_info(cirrus_cal->dev, "R :\t\t%d (%llu.%04llu Ohms)\n", rdc, ohms >> CS35L41_CAL_RDC_RADIX, (ohms & (((1 << CS35L41_CAL_RDC_RADIX) - 1))) * 10000 / (1 << CS35L41_CAL_RDC_RADIX)); dev_info(cirrus_cal->dev, "Checksum:\t%d\n", checksum); dev_info(cirrus_cal->dev, "Ambient:\t%d\n", temp); usleep_range(5000, 5500); /* Send STOP_PRE_REINIT command and poll for response */ regmap_write(regmap, CS35L41_CSPL_MBOX_CMD_DRV, CSPL_MBOX_CMD_STOP_PRE_REINIT); timeout = 100; do { dev_info(cirrus_cal->dev, "waiting for REINIT ready...\n"); usleep_range(1000, 1500); regmap_read(regmap, CS35L41_CSPL_MBOX_STS, &cal_state); } while ((cal_state != CSPL_MBOX_STS_RDY_FOR_REINIT) && --timeout > 0); usleep_range(5000, 5500); cs35l41_load_config(playback_config_filename, regmap); regmap_update_bits(regmap, CS35L41_MIXER_NGATE_CH1_CFG, CS35L41_NG_ENABLE_MASK, CS35L41_NG_ENABLE_MASK); regmap_update_bits(regmap, CS35L41_MIXER_NGATE_CH2_CFG, CS35L41_NG_ENABLE_MASK, CS35L41_NG_ENABLE_MASK); dev_dbg(cirrus_cal->dev, "NOISE GATE ENABLE\n"); regmap_write(regmap, CS35L41_CAL_STATUS, status); regmap_write(regmap, CS35L41_CAL_RDC, rdc); regmap_write(regmap, CS35L41_CAL_AMBIENT, temp); regmap_write(regmap, CS35L41_CAL_CHECKSUM, checksum); /* Send REINIT command and poll for response */ regmap_write(regmap, CS35L41_CSPL_MBOX_CMD_DRV, CSPL_MBOX_CMD_REINIT); timeout = 100; do { dev_info(cirrus_cal->dev, "waiting for REINIT done...\n"); usleep_range(1000, 1500); regmap_read(regmap, CS35L41_CSPL_MBOX_STS, &cal_state); } while ((cal_state != CSPL_MBOX_STS_RUNNING) && --timeout > 0); regmap_read(regmap, CS35L41_CSPL_STATE, &cal_state); if (cal_state == CS35L41_CSPL_STATE_ERROR) dev_err(cirrus_cal->dev, "Playback config load error\n"); cirrus_cal_unmute_dsp_inputs(regmap, index); dev_dbg(cirrus_cal->dev, "DSP Inputs unmuted\n"); cirrus_cal->cal_running = 0; cirrus_cal->efs_cache_read[index] = 0; kfree(playback_config_filename); } dev_dbg(cirrus_cal->dev, "Calibration complete\n"); mutex_unlock(&cirrus_cal->lock); } static void cirrus_cal_v_val_complete(void) { char *playback_config_filename; unsigned int cal_state; int timeout = 100, amp, index; struct regmap *regmap; const char *dsp_part_name; for (amp = 0; amp < cirrus_cal->num_amps; amp++) { regmap = cirrus_cal->amps[amp].regmap; dsp_part_name = cirrus_cal->amps[amp].dsp_part_name; index = cirrus_cal->amps[amp].index; 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, CS35L41_CSPL_MBOX_CMD_DRV, CSPL_MBOX_CMD_STOP_PRE_REINIT); timeout = 100; do { dev_info(cirrus_cal->dev, "waiting for REINIT ready...\n"); usleep_range(1000, 1500); regmap_read(regmap, CS35L41_CSPL_MBOX_STS, &cal_state); } while ((cal_state != CSPL_MBOX_STS_RDY_FOR_REINIT) && --timeout > 0); usleep_range(5000, 5500); cs35l41_load_config(playback_config_filename, regmap); regmap_update_bits(regmap, CS35L41_MIXER_NGATE_CH1_CFG, CS35L41_NG_ENABLE_MASK, CS35L41_NG_ENABLE_MASK); regmap_update_bits(regmap, CS35L41_MIXER_NGATE_CH2_CFG, CS35L41_NG_ENABLE_MASK, CS35L41_NG_ENABLE_MASK); dev_dbg(cirrus_cal->dev, "NOISE GATE ENABLE\n"); /* Send REINIT command and poll for response */ regmap_write(regmap, CS35L41_CSPL_MBOX_CMD_DRV, CSPL_MBOX_CMD_REINIT); timeout = 100; do { dev_info(cirrus_cal->dev, "waiting for REINIT done...\n"); usleep_range(1000, 1500); regmap_read(regmap, CS35L41_CSPL_MBOX_STS, &cal_state); } while ((cal_state != CSPL_MBOX_STS_RUNNING) && --timeout > 0); cirrus_cal_unmute_dsp_inputs(regmap, index); dev_dbg(cirrus_cal->dev, "DSP Inputs unmuted\n"); regmap_read(regmap, CS35L41_CSPL_STATE, &cal_state); if (cal_state == CS35L41_CSPL_STATE_ERROR) dev_err(cirrus_cal->dev, "Playback config load error\n"); kfree(playback_config_filename); } dev_info(cirrus_cal->dev, "V validation complete\n"); } static int cirrus_cal_get_power_temp(void) { union power_supply_propval value = {0}; struct power_supply *psy; psy = power_supply_get_by_name("battery"); if (!psy) { dev_warn(cirrus_cal->dev, "failed to get battery, assuming %d\n", CS34L40_CAL_AMBIENT_DEFAULT); return CS34L40_CAL_AMBIENT_DEFAULT; } power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &value); return DIV_ROUND_CLOSEST(value.intval, 10); } static void cirrus_cal_start(struct cirrus_mfd_amp *amp) { int ambient; unsigned int global_en; unsigned int halo_state; char *cal_config_filename; int timeout = 50; struct regmap *regmap = amp->regmap; const char *dsp_part_name = amp->dsp_part_name; 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(cirrus_cal->dev, "Calibration prepare start\n"); regmap_read(regmap, CS35L41_PWR_CTRL1, &global_en); while ((global_en & 1) == 0) { usleep_range(1000, 1500); regmap_read(regmap, CS35L41_PWR_CTRL1, &global_en); } do { dev_info(cirrus_cal->dev, "waiting for HALO start...\n"); usleep_range(10000, 15500); regmap_read(regmap, CS35L41_HALO_STATE, &halo_state); timeout--; } while ((halo_state == 0) && timeout > 0); if (timeout == 0) { dev_err(cirrus_cal->dev, "Failed to setup calibration\n"); kfree(cal_config_filename); return; } /* Send STOP_PRE_REINIT command and poll for response */ regmap_write(regmap, CS35L41_CSPL_MBOX_CMD_DRV, CSPL_MBOX_CMD_STOP_PRE_REINIT); timeout = 100; do { dev_info(cirrus_cal->dev, "waiting for REINIT ready...\n"); usleep_range(1000, 1500); regmap_read(regmap, CS35L41_CSPL_MBOX_STS, &halo_state); } while ((halo_state != CSPL_MBOX_STS_RDY_FOR_REINIT) && --timeout > 0); dev_dbg(cirrus_cal->dev, "load %s\n", dsp_part_name); cs35l41_load_config(cal_config_filename, regmap); cirrus_cal_mute_dsp_inputs(regmap, amp->index); dev_dbg(cirrus_cal->dev, "DSP Inputs muted\n"); regmap_update_bits(regmap, CS35L41_MIXER_NGATE_CH1_CFG, CS35L41_NG_ENABLE_MASK, 0); regmap_update_bits(regmap, CS35L41_MIXER_NGATE_CH2_CFG, CS35L41_NG_ENABLE_MASK, 0); dev_dbg(cirrus_cal->dev, "NOISE GATE DISABLE\n"); ambient = cirrus_cal_get_power_temp(); regmap_write(regmap, CS35L41_CAL_AMBIENT, ambient); /* Send REINIT command and poll for response */ regmap_write(regmap, CS35L41_CSPL_MBOX_CMD_DRV, CSPL_MBOX_CMD_REINIT); timeout = 100; do { dev_info(cirrus_cal->dev, "waiting for REINIT done...\n"); usleep_range(1000, 1500); regmap_read(regmap, CS35L41_CSPL_MBOX_STS, &halo_state); } while ((halo_state != CSPL_MBOX_STS_RUNNING) && --timeout > 0); kfree(cal_config_filename); } static int cirrus_cal_read_file(char *filename, int *value) { struct file *cal_filp; mm_segment_t old_fs = get_fs(); char str[12] = {0}; int ret; set_fs(get_ds()); cal_filp = filp_open(filename, O_RDONLY, 0660); if (IS_ERR(cal_filp)) { ret = PTR_ERR(cal_filp); dev_err(cirrus_cal->dev, "Failed to open calibration file %s: %d\n", filename, ret); goto err_open; } ret = vfs_read(cal_filp, (char __user *)str, sizeof(str), &cal_filp->f_pos); if (ret != sizeof(str)) { dev_err(cirrus_cal->dev, "Failed to read calibration file %s\n", filename); ret = -EIO; goto err_read; } ret = 0; if (kstrtoint(str, 0, value)) { dev_err(cirrus_cal->dev, "Failed to parse calibration.\n"); ret = -EINVAL; } err_read: filp_close(cal_filp, current->files); err_open: set_fs(old_fs); return ret; } int cirrus_cal_apply(const char *mfd_suffix) { unsigned int temp, rdc, status, checksum; int ret1 = 0; int ret2 = 0; struct cirrus_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(mfd_suffix); struct regmap *regmap = amp->regmap; char *efs_name; if (!cirrus_cal) return 0; if (!amp) return 0; efs_name = kzalloc(PAGE_SIZE, GFP_KERNEL); snprintf(efs_name, PAGE_SIZE, "%s%s", CIRRUS_CAL_RDC_SAVE_LOCATION, mfd_suffix); if (cirrus_cal->efs_cache_read[amp->index] == 1) { rdc = cirrus_cal->efs_cache_rdc[amp->index]; temp = cirrus_cal->efs_cache_temp; } else { ret1 = cirrus_cal_read_file(efs_name, &rdc); ret2 = cirrus_cal_read_file(CIRRUS_CAL_TEMP_SAVE_LOCATION, &temp); if (ret1 < 0 || ret2 < 0) { dev_err(cirrus_cal->dev, "No saved calibration, writing defaults\n"); rdc = CS34L40_CAL_RDC_DEFAULT; temp = CS34L40_CAL_AMBIENT_DEFAULT; } cirrus_cal->efs_cache_rdc[amp->index] = rdc; cirrus_cal->efs_cache_temp = temp; cirrus_cal->efs_cache_read[amp->index] = 1; } status = 1; checksum = status + rdc; dev_info(cirrus_cal->dev, "Writing calibration (%s): to cs35l41%s\n", efs_name, mfd_suffix); dev_info(cirrus_cal->dev, "RDC = %d, Temp = %d, Status = %d Checksum = %d\n", rdc, temp, status, checksum); regmap_write(regmap, CS35L41_CAL_RDC, rdc); regmap_write(regmap, CS35L41_CAL_AMBIENT, temp); regmap_write(regmap, CS35L41_CAL_STATUS, status); regmap_write(regmap, CS35L41_CAL_CHECKSUM, checksum); kfree(efs_name); return ret1 | ret2; } EXPORT_SYMBOL_GPL(cirrus_cal_apply); /***** 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", cirrus_cal->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 prepare; int ret = kstrtos32(buf, 10, &prepare); int delay = msecs_to_jiffies(CS35L41_CAL_COMPLETE_DELAY_MS); int retries = 5; unsigned int cal_state; int amp, index; struct regmap *regmap; if (cirrus_cal->cal_running) { dev_err(cirrus_cal->dev, "cirrus_cal measurement in progress\n"); return size; } mutex_lock(&cirrus_cal->lock); if (ret == 0 && prepare == 1) { cirrus_cal->cal_running = 1; for (amp = 0; amp < cirrus_cal->num_amps; amp++) { regmap = cirrus_cal->amps[amp].regmap; index = cirrus_cal->amps[amp].index; regmap_write(regmap, CS35L41_CAL_STATUS,0); regmap_write(regmap, CS35L41_CAL_RDC, 0); regmap_write(regmap, CS35L41_CAL_AMBIENT, 0); regmap_write(regmap, CS35L41_CAL_CHECKSUM,0); cirrus_cal_start(&cirrus_cal->amps[amp]); usleep_range(80000, 90000); regmap_read(regmap, CS35L41_CSPL_STATE, &cal_state); while (cal_state == CS35L41_CSPL_STATE_ERROR && retries > 0) { if (cal_state == CS35L41_CSPL_STATE_ERROR) { dev_err(cirrus_cal->dev, "Calibration load error\n"); } cirrus_cal_start(&cirrus_cal->amps[amp]); usleep_range(80000, 90000); regmap_read(regmap, CS35L41_CSPL_STATE, &cal_state); retries--; } if (retries == 0) { dev_err(cirrus_cal->dev, "Calibration setup fail @ %d\n", amp); mutex_unlock(&cirrus_cal->lock); cirrus_cal_unmute_dsp_inputs(regmap, index); cirrus_cal->cal_running = 0; return size; } } dev_dbg(cirrus_cal->dev, "Calibration prepare complete\n"); queue_delayed_work(system_unbound_wq, &cirrus_cal->cal_complete_work, delay); } mutex_unlock(&cirrus_cal->lock); return size; } static ssize_t cirrus_cal_v_status_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%s\n", cirrus_cal->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) { int prepare; int ret = kstrtos32(buf, 10, &prepare); int retries = 5; unsigned int cal_state; int i, reg, amp, index; struct regmap *regmap; unsigned int vmax[CIRRUS_MAX_AMPS]; unsigned int vmin[CIRRUS_MAX_AMPS]; unsigned int imax[CIRRUS_MAX_AMPS]; unsigned int imin[CIRRUS_MAX_AMPS]; if (cirrus_cal->cal_running) { dev_err(cirrus_cal->dev, "cirrus_cal measurement in progress\n"); return size; } mutex_lock(&cirrus_cal->lock); if (ret == 0 && prepare == 1) { cirrus_cal->cal_running = 1; for (amp = 0; amp < cirrus_cal->num_amps; amp++) { regmap = cirrus_cal->amps[amp].regmap; index = cirrus_cal->amps[amp].index; regmap_write(regmap, CIRRUS_PWR_CSPL_V_PEAK, 0); regmap_write(regmap, CIRRUS_PWR_CSPL_I_PEAK, 0); vmax[amp] = 0; vmin[amp] = INT_MAX; imax[amp] = 0; imin[amp] = INT_MAX; cirrus_cal_start(&cirrus_cal->amps[amp]); usleep_range(80000, 90000); regmap_read(regmap, CS35L41_CSPL_STATE, &cal_state); while (cal_state == CS35L41_CSPL_STATE_ERROR && retries > 0) { if (cal_state == CS35L41_CSPL_STATE_ERROR) { dev_err(cirrus_cal->dev, "Calibration load error\n"); } cirrus_cal_start(&cirrus_cal->amps[amp]); usleep_range(80000, 90000); regmap_read(regmap, CS35L41_CSPL_STATE, &cal_state); retries--; } if (retries == 0) { dev_err(cirrus_cal->dev, "Calibration setup fail @ %d\n", amp); mutex_unlock(&cirrus_cal->lock); cirrus_cal_unmute_dsp_inputs(regmap, index); cirrus_cal->cal_running = 0; return size; } } dev_info(cirrus_cal->dev, "V validation prepare complete\n"); for (i = 0; i < 400; i++) { for (amp = 0; amp < cirrus_cal->num_amps; amp++) { regmap = cirrus_cal->amps[amp].regmap; regmap_read(regmap, CIRRUS_PWR_CSPL_V_PEAK, ®); if (reg > vmax[amp]) vmax[amp] = reg; if (reg < vmin[amp]) vmin[amp] = reg; regmap_read(regmap, CIRRUS_PWR_CSPL_I_PEAK, ®); if (reg > imax[amp]) imax[amp] = reg; if (reg < imin[amp]) imin[amp] = reg; usleep_range(50, 150); } } for (amp = 0; amp < cirrus_cal->num_amps; amp++) { dev_info(cirrus_cal->dev, "V Validation results for cs35l41%s\n", cirrus_cal->amps[amp].mfd_suffix); dev_dbg(cirrus_cal->dev, "V Max: 0x%x\n", vmax[amp]); vmax[amp] = cirrus_cal_vpk_to_mv(vmax[amp]); dev_info(cirrus_cal->dev, "V Max: %d mV\n", vmax[amp]); dev_dbg(cirrus_cal->dev, "V Min: 0x%x\n", vmin[amp]); vmin[amp] = cirrus_cal_vpk_to_mv(vmin[amp]); dev_info(cirrus_cal->dev, "V Min: %d mV\n", vmin[amp]); dev_dbg(cirrus_cal->dev, "I Max: 0x%x\n", imax[amp]); imax[amp] = cirrus_cal_ipk_to_ma(imax[amp]); dev_info(cirrus_cal->dev, "I Max: %d mA\n", imax[amp]); dev_dbg(cirrus_cal->dev, "I Min: 0x%x\n", imin[amp]); imin[amp] = cirrus_cal_ipk_to_ma(imin[amp]); dev_info(cirrus_cal->dev, "I Min: %d mA\n", imin[amp]); if (vmax[amp] < CIRRUS_CAL_V_VAL_UB_MV && vmax[amp] > CIRRUS_CAL_V_VAL_LB_MV) { cirrus_cal->v_validation[amp] = 1; dev_info(cirrus_cal->dev, "V validation success\n"); } else { cirrus_cal->v_validation[amp] = 0xCC; dev_err(cirrus_cal->dev, "V validation failed\n"); } } cirrus_cal_v_val_complete(); cirrus_cal->cal_running = 0; } mutex_unlock(&cirrus_cal->lock); return size; } #ifdef CS35L41_FACTORY_RECOVERY_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); struct cirrus_mfd_amp *amp; if (cirrus_cal->cal_running) { dev_err(cirrus_cal->dev, "cirrus_cal measurement in progress\n"); return size; } mutex_lock(&cirrus_cal->lock); if (ret == 0 && reinit == 1) { for (i = 0; i < cirrus_cal->num_amps; i++) { amp = &cirrus_cal->amps[i]; if (amp && cirrus_cal->codecs[amp->index]) cs35l41_reinit(cirrus_cal->codecs[amp->index]); } } mutex_unlock(&cirrus_cal->lock); return size; } #endif /* CS35L41_FACTORY_RECOVERY_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_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(suffix); dev_info(dev, "%s\n", __func__); return sprintf(buf, "%d", cirrus_cal->v_validation[amp->index]); } 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_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(suffix); struct regmap *regmap = amp->regmap; if (amp) { regmap_read(regmap, CS35L41_CAL_RDC, &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_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(suffix); struct regmap *regmap = amp->regmap; ret = kstrtos32(buf, 10, &rdc); if (ret == 0 && amp) regmap_write(regmap, CS35L41_CAL_RDC, rdc); 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_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(suffix); struct regmap *regmap = amp->regmap; if (amp) { regmap_read(regmap, CS35L41_CAL_AMBIENT, &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_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(suffix); struct regmap *regmap = amp->regmap; ret = kstrtos32(buf, 10, &temp); if (ret == 0 && amp) regmap_write(regmap, CS35L41_CAL_AMBIENT, temp); 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_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(suffix); struct regmap *regmap = amp->regmap; if (amp) { regmap_read(regmap, CS35L41_CAL_CHECKSUM, &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_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(suffix); struct regmap *regmap = amp->regmap; ret = kstrtos32(buf, 10, &checksum); if (ret == 0 && amp) regmap_write(regmap, CS35L41_CAL_CHECKSUM, 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_mfd_amp *amp = cirrus_cal_get_amp_from_suffix(suffix); struct regmap *regmap = amp->regmap; if (amp) { regmap_read(regmap, CS35L41_CAL_SET_STATUS, &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 ssize_t cirrus_cal_rdc_stored_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned int rdc = 0; const char *suffix = &(attr->attr.name[strlen("rdc_stored")]); char *efs_name; efs_name = kzalloc(PAGE_SIZE, GFP_KERNEL); snprintf(efs_name, PAGE_SIZE, "%s%s", CIRRUS_CAL_RDC_SAVE_LOCATION, suffix); cirrus_cal_read_file(efs_name, &rdc); return sprintf(buf, "%d", rdc); } static ssize_t cirrus_cal_rdc_stored_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { return 0; } static ssize_t cirrus_cal_temp_stored_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned int temp_stored = 0; cirrus_cal_read_file(CIRRUS_CAL_TEMP_SAVE_LOCATION, &temp_stored); return sprintf(buf, "%d", temp_stored); } static ssize_t cirrus_cal_temp_stored_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); static DEVICE_ATTR(temp_stored, 0444, cirrus_cal_temp_stored_show, cirrus_cal_temp_stored_store); #ifdef CS35L41_FACTORY_RECOVERY_SYSFS static DEVICE_ATTR(reinit, 0664, cirrus_cal_reinit_show, cirrus_cal_reinit_store); #endif /* CS35L41_FACTORY_RECOVERY_SYSFS */ 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_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, }, { .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, .show = cirrus_cal_rdc_stored_show, .store = cirrus_cal_rdc_stored_store, }, }; static const char *generic_amp_attr_names[CIRRUS_CAL_NUM_ATTRS_AMP] = { "v_validation", "rdc", "temp", "checksum", "set_status", "rdc_stored" }; static struct attribute *cirrus_cal_attr_base[] = { &dev_attr_version.attr, &dev_attr_status.attr, &dev_attr_v_status.attr, &dev_attr_temp_stored.attr, #ifdef CS35L41_FACTORY_RECOVERY_SYSFS &dev_attr_reinit.attr, #endif /* CS35L41_FACTORY_RECOVERY_SYSFS */ NULL, }; /* Kernel does not allow attributes to be dynamically allocated */ 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]; 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); amp_attrs_new = &(amp_attrs_prealloc[index][0]); if (amp_attrs_new == NULL) return amp_attrs_new; 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(struct class *cirrus_amp_class, int num_amps, const char **mfd_suffixes) { int ret, i, j; struct device_attribute *new_attrs; cirrus_cal = kzalloc(sizeof(struct cirrus_cal_t), GFP_KERNEL); if (cirrus_cal == NULL) return -ENOMEM; cirrus_cal->amps = kzalloc(sizeof(struct cirrus_mfd_amp) * num_amps, GFP_KERNEL); if (cirrus_cal->amps == NULL) { kfree(cirrus_cal); return -ENOMEM; } cirrus_cal->num_amps = num_amps; for (i = 0; i < num_amps; i++) { cirrus_cal->amps[i].mfd_suffix = mfd_suffixes[i]; cirrus_cal->amps[i].index = i; cirrus_cal->efs_cache_read[i] = 0; cirrus_cal->v_validation[i] = 0; } cirrus_cal->cal_class = cirrus_amp_class; if (IS_ERR(cirrus_cal->cal_class)) { pr_err("Failed to register cirrus_cal\n"); ret = -EINVAL; goto err; } cirrus_cal->dev = device_create(cirrus_cal->cal_class, NULL, 1, NULL, CIRRUS_CAL_DIR_NAME); if (IS_ERR(cirrus_cal->dev)) { ret = PTR_ERR(cirrus_cal->dev); pr_err("Failed to create cirrus_cal device\n"); class_destroy(cirrus_cal->cal_class); goto err; } cirrus_cal_attr_grp.attrs = kzalloc(sizeof(struct attribute *) * (CIRRUS_CAL_NUM_ATTRS_AMP * num_amps + CIRRUS_CAL_NUM_ATTRS_BASE + 1), GFP_KERNEL); for (i = 0; i < num_amps; i++) { new_attrs = cirrus_cal_create_amp_attrs(mfd_suffixes[i], i); for (j = 0; j < CIRRUS_CAL_NUM_ATTRS_AMP; j++) { dev_dbg(cirrus_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; } } memcpy(&cirrus_cal_attr_grp.attrs[num_amps * CIRRUS_CAL_NUM_ATTRS_AMP], 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] = NULL; ret = sysfs_create_group(&cirrus_cal->dev->kobj, &cirrus_cal_attr_grp); if (ret) { dev_err(cirrus_cal->dev, "Failed to create sysfs group\n"); goto err; } mutex_init(&cirrus_cal->lock); INIT_DELAYED_WORK(&cirrus_cal->cal_complete_work, cirrus_cal_complete_work); return 0; err: kfree(cirrus_cal->amps); kfree(cirrus_cal); return ret; } void cirrus_cal_exit(void) { kfree(cirrus_cal->amps); kfree(cirrus_cal); }