| /* |
| * Calibration support for Cirrus Logic CS35L41 codec |
| * |
| * Copyright 2017 Cirrus Logic |
| * |
| * Author: David Rhodes <david.rhodes@cirrus.com> |
| * |
| * 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 <linux/module.h> |
| #include <linux/miscdevice.h> |
| #include <linux/device.h> |
| #include <linux/uaccess.h> |
| #include <linux/delay.h> |
| #include <linux/regmap.h> |
| #include <linux/slab.h> |
| #include <linux/syscalls.h> |
| #include <linux/file.h> |
| #include <linux/fcntl.h> |
| #include <linux/fs.h> |
| #include <linux/init.h> |
| #include <linux/platform_device.h> |
| #include <asm/io.h> |
| #include <linux/firmware.h> |
| #include <linux/vmalloc.h> |
| #include <linux/workqueue.h> |
| #include <linux/power_supply.h> |
| #include <linux/fs.h> |
| |
| #include <sound/cs35l41.h> |
| #include <linux/mfd/cs35l41/core.h> |
| #include <linux/mfd/cs35l41/registers.h> |
| #include <linux/mfd/cs35l41/calibration.h> |
| #include <linux/mfd/cs35l41/wmfw.h> |
| |
| #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); |
| } |
| |
| |