blob: 39ec80e01f76524d354346d86cbd984c79497ba3 [file] [edit]
// SPDX-License-Identifier: GPL-2.0
//
// CS40L20/CS40L25/CS40L25A/CS40L25B Haptics Driver
//
// Copyright (C) 2018-2020 Cirrus Logic, Inc.
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/string.h>
#include <linux/workqueue.h>
#include <linux/regulator/consumer.h>
#include <linux/regmap.h>
#include <linux/sysfs.h>
#include <linux/firmware.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/hrtimer.h>
#include <linux/mfd/core.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/platform_data/cs40l2x.h>
#include <linux/mfd/cs40l2x.h>
#include <linux/uaccess.h>
#ifdef CONFIG_ANDROID_TIMED_OUTPUT
#include "../staging/android/timed_output.h"
#else
#include <linux/leds.h>
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
static const char * const cs40l2x_supplies[] = {
"VA",
"VP",
};
static const char * const cs40l2x_part_nums[] = {
"CS40L20",
"CS40L25",
"CS40L25A",
"CS40L25B",
};
static const char * const cs40l2x_event_regs[] = {
"GPIO1EVENT",
"GPIO2EVENT",
"GPIO3EVENT",
"GPIO4EVENT",
"GPIOPLAYBACKEVENT",
"TRIGGERPLAYBACKEVENT",
"RXREADYEVENT",
"HARDWAREEVENT",
};
static const unsigned int cs40l2x_event_masks[] = {
CS40L2X_EVENT_GPIO1_ENABLED,
CS40L2X_EVENT_GPIO2_ENABLED,
CS40L2X_EVENT_GPIO3_ENABLED,
CS40L2X_EVENT_GPIO4_ENABLED,
CS40L2X_EVENT_START_ENABLED | CS40L2X_EVENT_END_ENABLED,
CS40L2X_EVENT_START_ENABLED | CS40L2X_EVENT_END_ENABLED,
CS40L2X_EVENT_READY_ENABLED,
CS40L2X_EVENT_HARDWARE_ENABLED,
};
static int cs40l2x_raw_write(struct cs40l2x_private *cs40l2x, unsigned int reg,
const void *val, size_t val_len, size_t limit);
static int cs40l2x_hw_err_rls(struct cs40l2x_private *cs40l2x,
unsigned int irq_mask);
static int cs40l2x_hw_err_chk(struct cs40l2x_private *cs40l2x);
static int cs40l2x_basic_mode_exit(struct cs40l2x_private *cs40l2x);
static int cs40l2x_firmware_swap(struct cs40l2x_private *cs40l2x,
unsigned int fw_id);
static int cs40l2x_wavetable_swap(struct cs40l2x_private *cs40l2x,
const char *wt_file);
static int cs40l2x_wavetable_sync(struct cs40l2x_private *cs40l2x);
static unsigned int cs40l2x_dsp_reg(struct cs40l2x_private *cs40l2x,
const char *coeff_name, const unsigned int block_type,
const unsigned int algo_id);
static int cs40l2x_dsp_cache(struct cs40l2x_private *cs40l2x,
unsigned int reg, unsigned int val);
static const struct cs40l2x_fw_desc *cs40l2x_firmware_match(
struct cs40l2x_private *cs40l2x, unsigned int fw_id)
{
int i;
for (i = 0; i < CS40L2X_NUM_FW_FAMS; i++)
if (cs40l2x_fw_fam[i].id == fw_id)
return &cs40l2x_fw_fam[i];
dev_err(cs40l2x->dev, "No matching firmware for ID 0x%06X\n", fw_id);
return NULL;
}
static struct cs40l2x_private *cs40l2x_get_private(struct device *dev)
{
#ifdef CONFIG_ANDROID_TIMED_OUTPUT
/* timed output device does not register under a parent device */
return container_of(dev_get_drvdata(dev),
struct cs40l2x_private, timed_dev);
#else
return dev_get_drvdata(dev);
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
}
static inline int cs40l2x_ground_amp(struct cs40l2x_private *cs40l2x, bool gnd)
{
unsigned int val = CS40L2X_FORCE_SPK_FREE;
if (!cs40l2x->amp_gnd_stby)
return 0;
if (gnd)
val = CS40L2X_FORCE_SPK_GND;
return regmap_write(cs40l2x->regmap, CS40L2X_SPK_FORCE_TST_1, val);
}
static void cs40l2x_sysfs_notify(struct cs40l2x_private *cs40l2x,
const char *attr)
{
struct kobject *kobj;
#ifdef CONFIG_HAPTICS_CS40L2X_INPUT
kobj = &cs40l2x->input->dev.kobj;
#elif defined CONFIG_ANDROID_TIMED_OUTPUT
kobj = &cs40l2x->timed_dev.dev->kobj;
#else
kobj = &cs40l2x->dev->kobj;
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
sysfs_notify(kobj, NULL, attr);
}
struct dspmem_chunk {
u8 *data;
u8 *max;
int bytes;
u32 cache;
int cachebits;
};
static inline struct dspmem_chunk dspmem_chunk(u8 *data, int size)
{
struct dspmem_chunk ch = {
.data = data,
.max = data + size,
};
return ch;
}
static inline bool dspmem_chunk_end(struct dspmem_chunk *ch)
{
return ch->data == ch->max;
}
static inline int dspmem_chunk_bytes(struct dspmem_chunk *ch)
{
return ch->bytes;
}
static int dspmem_chunk_write(struct dspmem_chunk *ch, int nbits, u32 val)
{
int nwrite, i;
nwrite = min(24 - ch->cachebits, nbits);
ch->cache <<= nwrite;
ch->cache |= val >> (nbits - nwrite);
ch->cachebits += nwrite;
nbits -= nwrite;
if (ch->cachebits == 24) {
if (dspmem_chunk_end(ch))
return -ENOSPC;
ch->cache &= 0xFFFFFF;
for (i = 0; i < sizeof(ch->cache); i++, ch->cache <<= 8)
*ch->data++ = (ch->cache & 0xFF000000) >> 24;
ch->bytes += sizeof(ch->cache);
ch->cachebits = 0;
}
if (nbits)
return dspmem_chunk_write(ch, nbits, val);
return 0;
}
static int dspmem_chunk_flush(struct dspmem_chunk *ch)
{
if (!ch->cachebits)
return 0;
return dspmem_chunk_write(ch, 24 - ch->cachebits, 0);
}
static int cs40l2x_write_comp(struct cs40l2x_private *cs40l2x, void *buf,
int size, struct wt_type10_comp *wave)
{
struct dspmem_chunk ch = dspmem_chunk(buf, size);
int i;
dspmem_chunk_write(&ch, 24, wave->wlength);
dspmem_chunk_write(&ch, 8, 0); // padding
dspmem_chunk_write(&ch, 8, wave->nsections);
dspmem_chunk_write(&ch, 8, wave->repeat);
for (i = 0; i < wave->nsections; i++) {
dspmem_chunk_write(&ch, 8, wave->sections[i].amplitude);
dspmem_chunk_write(&ch, 8, wave->sections[i].index);
dspmem_chunk_write(&ch, 8, wave->sections[i].repeat);
dspmem_chunk_write(&ch, 8, wave->sections[i].flags);
dspmem_chunk_write(&ch, 16, wave->sections[i].delay);
if (wave->sections[i].flags & WT_T10_FLAG_DURATION) {
dspmem_chunk_write(&ch, 8, 0); // padding
dspmem_chunk_write(&ch, 16, wave->sections[i].duration);
}
}
return dspmem_chunk_bytes(&ch);
}
static int cs40l2x_write_pwle(struct cs40l2x_private *cs40l2x, void *buf,
int size, struct wt_type12_pwle *wave)
{
struct dspmem_chunk ch = dspmem_chunk(buf, size);
int i;
dspmem_chunk_write(&ch, 24, wave->wlength);
dspmem_chunk_write(&ch, 8, wave->repeat);
dspmem_chunk_write(&ch, 12, wave->wait);
dspmem_chunk_write(&ch, 8, wave->nsections);
for (i = 0; i < wave->nsections; i++) {
dspmem_chunk_write(&ch, 16, wave->sections[i].time);
dspmem_chunk_write(&ch, 12, wave->sections[i].level);
dspmem_chunk_write(&ch, 12, wave->sections[i].frequency);
dspmem_chunk_write(&ch, 8, wave->sections[i].flags);
if (wave->sections[i].flags & WT_T12_FLAG_AMP_REG)
dspmem_chunk_write(&ch, 24, wave->sections[i].vbtarget);
}
dspmem_chunk_flush(&ch);
return dspmem_chunk_bytes(&ch);
}
void cs40l2x_set_state(struct cs40l2x_private *cs40l2x, bool state)
{
if (cs40l2x->vibe_state != state) {
cs40l2x->vibe_state = state;
cs40l2x_sysfs_notify(cs40l2x, "vibe_state");
}
}
EXPORT_SYMBOL(cs40l2x_set_state);
ssize_t strscpy_pad(char *dest, const char *src, size_t count)
{
ssize_t written;
written = strscpy(dest, src, count);
if (written < 0 || written == count - 1)
return written;
memset(dest + written + 1, 0, count - written - 1);
return written;
}
static void cs40l2x_set_gpio_event(struct cs40l2x_private *cs40l2x, bool value)
{
if (cs40l2x->gpio_event != value) {
cs40l2x->gpio_event = value;
cs40l2x_sysfs_notify(cs40l2x, "gpio_event");
}
}
static void cs40l2x_set_safe_save_state(struct cs40l2x_private *cs40l2x,
bool state)
{
if (cs40l2x->safe_save_state != state) {
cs40l2x->safe_save_state = state;
cs40l2x_sysfs_notify(cs40l2x, "safe_save_state");
}
}
static int cs40l2x_check_wt_open_space(struct cs40l2x_private *cs40l2x,
unsigned int size)
{
unsigned int default_empty_ym_size = (cs40l2x->wt_limit_ym -
CS40L2X_WT_YM_EMPTY_SIZE);
/* If YM exists, must add to end of YM to keep index order */
if (cs40l2x->wt_open_ym < default_empty_ym_size) {
if (size <= cs40l2x->wt_open_ym) {
/* Add to end of existing YM */
cs40l2x->create_ym = false;
cs40l2x->xm_append = false;
return 0;
}
/* YM exists, requested size too big */
if (size > cs40l2x->wt_open_ym)
return -ENOSPC;
}
if (size <= cs40l2x->wt_open_xm) {
/* Add to end of existing XM */
cs40l2x->create_ym = false;
cs40l2x->xm_append = true;
return 0;
}
if (size > cs40l2x->wt_open_xm) {
/* Create YM section and add to YM */
cs40l2x->create_ym = true;
cs40l2x->xm_append = false;
return 0;
}
return -ENOSPC;
}
static void cs40l2x_set_ym_data(struct cs40l2x_private *cs40l2x)
{
cs40l2x->ym_hdr_strt_pos = (cs40l2x->wt_xm_size +
CS40L2X_WT_YM_PRE_HDR_BYTES +
CS40L2X_WT_DBLK_LENGTH_SIZE);
cs40l2x->wt_ym_size = (CS40L2X_WT_YM_PRE_HDR_BYTES +
CS40L2X_WT_DBLK_LENGTH_SIZE +
(CS40L2X_WT_HEADER_ENTRY_SIZE *
CS40L2X_WT_NUM_VIRT_SLOTS) +
CS40L2X_WT_TERMINATOR_BYTES +
cs40l2x->comp_bytes);
}
static int cs40l2x_create_wvfrm_len_type_pairs(struct cs40l2x_private *cs40l2x)
{
unsigned int wt_file_type;
unsigned int wt_offset = 0;
unsigned int wt_length = 0;
unsigned int waveform_length;
unsigned int cleared_extra_bits;
unsigned int pos, offset_pos;
int i, wt_entry_words;
int count = 0;
if (memcmp(cs40l2x->pbq_fw_raw_wt, "WMDR", 4)) {
dev_err(cs40l2x->dev, "Failed to recognize raw wavetable\n");
return -ENODATA;
}
if (cs40l2x->create_ym)
cs40l2x_set_ym_data(cs40l2x);
memset(&cs40l2x->wvfrm_lengths[0], 0,
cs40l2x->wvfrm_lengths_size);
cs40l2x->updated_offsets_size = cs40l2x->num_waves;
pos = cs40l2x->xm_hdr_strt_pos;
for (i = 0; i < cs40l2x->num_xm_wavs; i++) {
wt_file_type = (cs40l2x->pbq_fw_raw_wt[pos] << 24)
+ (cs40l2x->pbq_fw_raw_wt[pos + 1] << 16)
+ (cs40l2x->pbq_fw_raw_wt[pos + 2] << 8)
+ (cs40l2x->pbq_fw_raw_wt[pos + 3]);
cleared_extra_bits = (wt_file_type & CS40L2X_WT_CLR_EX_TYPE);
cs40l2x->wvfrm_lengths[count] = cleared_extra_bits;
pos += 4;
wt_offset = (cs40l2x->pbq_fw_raw_wt[pos] << 24)
+ (cs40l2x->pbq_fw_raw_wt[pos + 1] << 16)
+ (cs40l2x->pbq_fw_raw_wt[pos + 2] << 8)
+ (cs40l2x->pbq_fw_raw_wt[pos + 3]);
/* Add words to updated offset for new entry.
* Wavetable entries are in words.
*/
wt_entry_words = ((CS40L2X_WT_HEADER_ENTRY_SIZE *
CS40L2X_WT_NUM_VIRT_SLOTS) / 4);
cs40l2x->updated_offsets[i] = wt_offset + wt_entry_words;
pos += 4;
wt_length = (cs40l2x->pbq_fw_raw_wt[pos] << 24)
+ (cs40l2x->pbq_fw_raw_wt[pos + 1] << 16)
+ (cs40l2x->pbq_fw_raw_wt[pos + 2] << 8)
+ (cs40l2x->pbq_fw_raw_wt[pos + 3]);
offset_pos = cs40l2x->xm_hdr_strt_pos +
(wt_offset * 4);
waveform_length =
(cs40l2x->pbq_fw_raw_wt[offset_pos] << 24)
+ (cs40l2x->pbq_fw_raw_wt[offset_pos + 1] << 16)
+ (cs40l2x->pbq_fw_raw_wt[offset_pos + 2] << 8)
+ (cs40l2x->pbq_fw_raw_wt[offset_pos + 3]);
cs40l2x->wvfrm_lengths[count + 1] = waveform_length;
count = count + 2;
pos += 4;
}
cs40l2x->wt_xm_header_end_pos = pos;
cs40l2x->wt_xm_header_last_offset = wt_offset;
cs40l2x->wt_xm_header_last_size = wt_length;
if (cs40l2x->num_ym_wavs > 0) {
pos = cs40l2x->ym_hdr_strt_pos;
for (i = cs40l2x->num_xm_wavs;
i < (cs40l2x->num_xm_wavs +
cs40l2x->num_ym_wavs); i++) {
wt_file_type = (cs40l2x->pbq_fw_raw_wt[pos] << 24)
+ (cs40l2x->pbq_fw_raw_wt[pos + 1] << 16)
+ (cs40l2x->pbq_fw_raw_wt[pos + 2] << 8)
+ (cs40l2x->pbq_fw_raw_wt[pos + 3]);
cleared_extra_bits =
(wt_file_type & CS40L2X_WT_CLR_EX_TYPE);
cs40l2x->wvfrm_lengths[count] = cleared_extra_bits;
pos += 4;
wt_offset = (cs40l2x->pbq_fw_raw_wt[pos] << 24)
+ (cs40l2x->pbq_fw_raw_wt[pos + 1] << 16)
+ (cs40l2x->pbq_fw_raw_wt[pos + 2] << 8)
+ (cs40l2x->pbq_fw_raw_wt[pos + 3]);
/* Add words to updated offset for new entry.
* Wavetable entries are in words.
*/
wt_entry_words = ((CS40L2X_WT_HEADER_ENTRY_SIZE *
CS40L2X_WT_NUM_VIRT_SLOTS) / 4);
cs40l2x->updated_offsets[i] = wt_offset +
wt_entry_words;
pos += 4;
wt_length = (cs40l2x->pbq_fw_raw_wt[pos] << 24)
+ (cs40l2x->pbq_fw_raw_wt[pos + 1] << 16)
+ (cs40l2x->pbq_fw_raw_wt[pos + 2] << 8)
+ (cs40l2x->pbq_fw_raw_wt[pos + 3]);
offset_pos = cs40l2x->ym_hdr_strt_pos +
(wt_offset * 4);
waveform_length =
(cs40l2x->pbq_fw_raw_wt[offset_pos] << 24)
+ (cs40l2x->pbq_fw_raw_wt[offset_pos + 1] << 16)
+ (cs40l2x->pbq_fw_raw_wt[offset_pos + 2] << 8)
+ (cs40l2x->pbq_fw_raw_wt[offset_pos + 3]);
cs40l2x->wvfrm_lengths[count + 1] = waveform_length;
count = count + 2;
pos += 4;
}
cs40l2x->wt_ym_header_end_pos = pos;
cs40l2x->wt_ym_header_last_offset = wt_offset;
cs40l2x->wt_ym_header_last_size = wt_length;
}
return 0;
}
static int cs40l2x_to_bytes_msb(unsigned int val, int len, char **byte_data)
{
char two_bytes[2] = {0, 0};
char three_bytes[3] = {0, 0, 0};
int i;
int count = 0;
if ((len < 2) || (len > 3))
return -EINVAL;
for (i = (len - 1); i >= 0; i--) {
if (len == 2)
two_bytes[count] = (val >> (i * 8)) & 0xFF;
if (len == 3)
three_bytes[count] = (val >> (i * 8)) & 0xFF;
count++;
}
if (len == 2) {
(*byte_data)[0] = two_bytes[0];
(*byte_data)[1] = two_bytes[1];
}
if (len == 3) {
(*byte_data)[0] = three_bytes[0];
(*byte_data)[1] = three_bytes[1];
(*byte_data)[2] = three_bytes[2];
}
return 0;
}
static int cs40l2x_insert_comp_wt_header(struct cs40l2x_private *cs40l2x,
bool is_xm, unsigned int comp_size, unsigned int pos)
{
unsigned int i_pos = pos;
unsigned int offset;
char *wt_file_type;
char *wt_offset;
char *wt_length;
int wt_entry_words = ((CS40L2X_WT_HEADER_ENTRY_SIZE *
CS40L2X_WT_NUM_VIRT_SLOTS) / 4);
unsigned int indv_comp_size = (comp_size / CS40L2X_WT_NUM_VIRT_SLOTS);
int i;
cs40l2x->three_bytes[0] = 0;
cs40l2x->three_bytes[1] = 0;
cs40l2x->three_bytes[2] = 0;
wt_file_type = cs40l2x->three_bytes;
wt_offset = cs40l2x->three_bytes;
wt_length = cs40l2x->three_bytes;
if (is_xm) {
offset = cs40l2x->wt_xm_header_last_offset +
cs40l2x->wt_xm_header_last_size +
wt_entry_words;
} else {
offset = cs40l2x->wt_ym_header_last_offset +
cs40l2x->wt_ym_header_last_size +
wt_entry_words;
}
for (i = 0; i < CS40L2X_WT_NUM_VIRT_SLOTS; i++) {
cs40l2x_to_bytes_msb(CS40L2X_WT_TYPE_10_COMP_FILE,
CS40L2X_WT_WORD_SIZE, &wt_file_type);
cs40l2x->pbq_updated_fw_raw_wt[i_pos] = 0; /* Zero pad */
cs40l2x->pbq_updated_fw_raw_wt[i_pos + 1] = wt_file_type[0];
cs40l2x->pbq_updated_fw_raw_wt[i_pos + 2] = wt_file_type[1];
cs40l2x->pbq_updated_fw_raw_wt[i_pos + 3] = wt_file_type[2];
i_pos += 4;
cs40l2x_to_bytes_msb(offset,
CS40L2X_WT_WORD_SIZE, &wt_offset);
cs40l2x->pbq_updated_fw_raw_wt[i_pos] = 0; /* Zero pad */
cs40l2x->pbq_updated_fw_raw_wt[i_pos + 1] = wt_offset[0];
cs40l2x->pbq_updated_fw_raw_wt[i_pos + 2] = wt_offset[1];
cs40l2x->pbq_updated_fw_raw_wt[i_pos + 3] = wt_offset[2];
i_pos += 4;
cs40l2x_to_bytes_msb((indv_comp_size / 4),
CS40L2X_WT_WORD_SIZE, &wt_length);
cs40l2x->pbq_updated_fw_raw_wt[i_pos] = 0; /* Zero pad */
cs40l2x->pbq_updated_fw_raw_wt[i_pos + 1] = wt_length[0];
cs40l2x->pbq_updated_fw_raw_wt[i_pos + 2] = wt_length[1];
cs40l2x->pbq_updated_fw_raw_wt[i_pos + 3] = wt_length[2];
i_pos += 4;
offset += (indv_comp_size / 4);
}
return i_pos;
}
static void cs40l2x_update_wt_header_offsets(struct cs40l2x_private *cs40l2x,
bool is_xm)
{
char *wt_offset;
unsigned int pos = 0;
unsigned int offset;
unsigned int num_wavs;
int i;
cs40l2x->three_bytes[0] = 0;
cs40l2x->three_bytes[1] = 0;
cs40l2x->three_bytes[2] = 0;
wt_offset = cs40l2x->three_bytes;
if (is_xm)
num_wavs = cs40l2x->num_xm_wavs;
else
num_wavs = cs40l2x->num_ym_wavs;
for (i = 0; i < num_wavs; i++) {
pos += 4;
offset = cs40l2x->updated_offsets[i];
if (!is_xm) {
offset =
cs40l2x->updated_offsets[i + cs40l2x->num_xm_wavs];
}
wt_offset[0] = 0;
wt_offset[1] = 0;
wt_offset[2] = 0;
cs40l2x_to_bytes_msb(offset, CS40L2X_WT_WORD_SIZE,
&wt_offset);
cs40l2x->pbq_updated_fw_raw_wt[pos] = 0; /* Zero pad */
cs40l2x->pbq_updated_fw_raw_wt[pos + 1] = wt_offset[0];
cs40l2x->pbq_updated_fw_raw_wt[pos + 2] = wt_offset[1];
cs40l2x->pbq_updated_fw_raw_wt[pos + 3] = wt_offset[2];
pos += 8;
}
}
static int cs40l2x_create_wt_header(struct cs40l2x_private *cs40l2x,
bool is_xm)
{
unsigned int start_pos;
unsigned int header_length;
if (is_xm) {
header_length = (cs40l2x->num_xm_wavs *
CS40L2X_WT_HEADER_ENTRY_SIZE);
start_pos = cs40l2x->xm_hdr_strt_pos;
} else {
header_length = (cs40l2x->num_ym_wavs *
CS40L2X_WT_HEADER_ENTRY_SIZE);
start_pos = cs40l2x->ym_hdr_strt_pos;
}
memcpy(&cs40l2x->pbq_updated_fw_raw_wt[0],
&cs40l2x->pbq_fw_raw_wt[start_pos],
header_length);
return header_length;
}
static int cs40l2x_copy_waveform_data(struct cs40l2x_private *cs40l2x,
bool is_xm, unsigned int pos)
{
unsigned int wav_data_end;
unsigned int i_pos = pos;
unsigned int start;
int i;
if (is_xm) {
wav_data_end = cs40l2x->xm_hdr_strt_pos +
((cs40l2x->wt_xm_header_last_offset +
cs40l2x->wt_xm_header_last_size) *
CS40L2X_WT_TOTAL_WORD_SIZE);
start = cs40l2x->wt_xm_header_end_pos;
} else {
wav_data_end = cs40l2x->ym_hdr_strt_pos +
((cs40l2x->wt_ym_header_last_offset +
cs40l2x->wt_ym_header_last_size) *
CS40L2X_WT_TOTAL_WORD_SIZE);
start = cs40l2x->wt_ym_header_end_pos;
}
for (i = start; i < wav_data_end; i++) {
cs40l2x->pbq_updated_fw_raw_wt[i_pos] =
cs40l2x->pbq_fw_raw_wt[i];
i_pos++;
}
return i_pos;
}
static int cs40l2x_update_existing_block(struct cs40l2x_private *cs40l2x,
unsigned int comp_size, bool is_xm)
{
unsigned int pos;
unsigned int end_data_pos;
unsigned int end_header_pos;
unsigned int existing_wt_size;
unsigned int wav_data_end;
unsigned int pbq_updated_fw_raw_wt_size;
unsigned int start;
if (is_xm) {
wav_data_end = cs40l2x->xm_hdr_strt_pos +
((cs40l2x->wt_xm_header_last_offset +
cs40l2x->wt_xm_header_last_size) *
CS40L2X_WT_TOTAL_WORD_SIZE);
start = cs40l2x->xm_hdr_strt_pos;
} else {
wav_data_end = cs40l2x->ym_hdr_strt_pos +
((cs40l2x->wt_ym_header_last_offset +
cs40l2x->wt_ym_header_last_size) *
CS40L2X_WT_TOTAL_WORD_SIZE);
start = cs40l2x->ym_hdr_strt_pos;
}
existing_wt_size = (wav_data_end - start);
pbq_updated_fw_raw_wt_size = (existing_wt_size +
(comp_size + (CS40L2X_WT_HEADER_ENTRY_SIZE *
CS40L2X_WT_NUM_VIRT_SLOTS)));
/* Copies wt header up to position before wt header terminator */
pos = cs40l2x_create_wt_header(cs40l2x, is_xm);
/* Update the offsets to account for the extra wt header entries */
cs40l2x_update_wt_header_offsets(cs40l2x, is_xm);
/* Insert the new composite wt header entry
* after the last wt header entry
* and before the wt header terminator
*/
end_header_pos =
cs40l2x_insert_comp_wt_header(cs40l2x, is_xm, comp_size, pos);
/* Copy waveform data */
end_data_pos =
cs40l2x_copy_waveform_data(cs40l2x, is_xm, end_header_pos);
end_data_pos += comp_size;
cs40l2x->updated_block_size = end_data_pos;
if (cs40l2x->updated_block_size > pbq_updated_fw_raw_wt_size) {
dev_err(cs40l2x->dev, "Virtual block copy failed.\n");
return -EINVAL;
}
return 0;
}
static int cs40l2x_create_block(struct cs40l2x_private *cs40l2x,
unsigned int comp_size)
{
char *wt_offset;
char *wvfrm_size;
unsigned int pos;
unsigned int offset;
unsigned int end_data_pos;
unsigned int indv_comp_size = (comp_size / CS40L2X_WT_NUM_VIRT_SLOTS);
unsigned int pbq_updated_fw_raw_wt_size;
int i, count = 0, header_slot = 0;
cs40l2x->two_bytes[0] = 0;
cs40l2x->two_bytes[1] = 0;
wt_offset = cs40l2x->two_bytes;
wvfrm_size = cs40l2x->two_bytes;
pbq_updated_fw_raw_wt_size = (CS40L2X_WT_TERMINATOR_BYTES +
(comp_size + (CS40L2X_WT_HEADER_ENTRY_SIZE *
CS40L2X_WT_NUM_VIRT_SLOTS)));
/* Create the new wt header */
offset = (((CS40L2X_WT_HEADER_ENTRY_SIZE *
CS40L2X_WT_NUM_VIRT_SLOTS) +
CS40L2X_WT_TERMINATOR_BYTES) / 4);
for (i = 0; i < (CS40L2X_WT_HEADER_ENTRY_SIZE *
CS40L2X_WT_NUM_VIRT_SLOTS); i++) {
if ((count + 1) % 4 == 0) {
header_slot++;
if (header_slot == 1) {
cs40l2x->pbq_updated_fw_raw_wt[i] =
CS40L2X_WT_TYPE_10_COMP_FILE;
} else if (header_slot == 2) {
wt_offset[0] = 0;
wt_offset[1] = 0;
cs40l2x_to_bytes_msb(offset,
2, &wt_offset);
cs40l2x->pbq_updated_fw_raw_wt[i - 1] =
wt_offset[0];
cs40l2x->pbq_updated_fw_raw_wt[i] =
wt_offset[1];
} else if (header_slot == 3) {
wvfrm_size[0] = 0;
wvfrm_size[1] = 0;
cs40l2x_to_bytes_msb(
(indv_comp_size / 4), 2, &wvfrm_size);
cs40l2x->pbq_updated_fw_raw_wt[i - 1] =
wvfrm_size[0];
cs40l2x->pbq_updated_fw_raw_wt[i] =
wvfrm_size[1];
header_slot = 0;
offset += (indv_comp_size / 4);
}
} else {
cs40l2x->pbq_updated_fw_raw_wt[i] = CS40L2X_WT_ZERO;
}
count++;
}
/* Add new wt header terminator */
pos = (CS40L2X_WT_HEADER_ENTRY_SIZE *
CS40L2X_WT_NUM_VIRT_SLOTS);
cs40l2x->pbq_updated_fw_raw_wt[pos] = CS40L2X_WT_ZERO;
pos++;
for (i = pos; i < (pos + (CS40L2X_WT_TERMINATOR_BYTES - 1)); i++)
cs40l2x->pbq_updated_fw_raw_wt[i] =
CS40L2X_WT_TERMINATOR_BYTE;
end_data_pos = ((pos + (CS40L2X_WT_TERMINATOR_BYTES - 1)) + comp_size);
cs40l2x->updated_block_size = end_data_pos;
cs40l2x->ym_hdr_strt_pos = 0;
cs40l2x->wt_ym_header_end_pos = 0;
if (!cs40l2x->ym_hdr_strt_reg) {
dev_err(cs40l2x->dev,
"Malformed bin file, missing YM section header.\n");
return -EINVAL;
}
if (cs40l2x->updated_block_size > pbq_updated_fw_raw_wt_size) {
dev_err(cs40l2x->dev, "Failed to create virtual block.\n");
return -EINVAL;
}
return 0;
}
static int cs40l2x_write_virtual_waveform(struct cs40l2x_private *cs40l2x,
unsigned int index, bool is_gpio, bool is_rise, bool over_write)
{
bool is_xm;
int ret = 0;
char *raw_waveform_data;
unsigned int last_offset, size_in_words, wvfrm_type;
unsigned int num_xm_registers, num_ym_registers;
unsigned int size_in_bytes = 0;
unsigned int wt_xm_header_end_reg, wt_ym_header_end_reg;
unsigned int len_reg, off_reg, type_reg, data_reg;
struct regmap *regmap = cs40l2x->regmap;
struct cs40l2x_virtual_waveform *virtual_wav;
unsigned int reg_addr_size = CS40L2X_WT_TOTAL_WORD_SIZE;
if (over_write) {
is_xm = cs40l2x->ovwr_wav->is_xm;
wvfrm_type = cs40l2x->ovwr_wav->wvfrm_type +
cs40l2x->ovwr_wav->wvfrm_feature;
size_in_bytes = cs40l2x->ovwr_wav->data_len;
raw_waveform_data = devm_kzalloc(cs40l2x->dev,
size_in_bytes, GFP_KERNEL);
if (!raw_waveform_data)
return -ENOMEM;
memcpy(raw_waveform_data, &cs40l2x->ovwr_wav->data[0],
cs40l2x->ovwr_wav->data_len);
} else {
list_for_each_entry(virtual_wav,
&cs40l2x->virtual_waveform_head, list) {
if (virtual_wav->index != index)
continue;
is_xm = virtual_wav->is_xm;
wvfrm_type = virtual_wav->wvfrm_type +
virtual_wav->wvfrm_feature;
size_in_bytes = virtual_wav->data_len;
raw_waveform_data = devm_kzalloc(cs40l2x->dev,
size_in_bytes, GFP_KERNEL);
if (!raw_waveform_data)
return -ENOMEM;
memcpy(raw_waveform_data, &virtual_wav->data[0],
virtual_wav->data_len);
break;
}
if (size_in_bytes == 0) {
dev_err(cs40l2x->dev,
"Unable to find index in virtual list\n");
return -EINVAL;
}
}
/* wt_xm_header_end_pos is last byte before header terminator */
if (is_xm) {
num_xm_registers = ((cs40l2x->wt_xm_header_end_pos +
(CS40L2X_WT_NUM_VIRT_SLOTS *
CS40L2X_WT_HEADER_ENTRY_SIZE)) -
cs40l2x->xm_hdr_strt_pos);
wt_xm_header_end_reg = (cs40l2x->xm_hdr_strt_reg +
num_xm_registers);
len_reg = (wt_xm_header_end_reg - reg_addr_size);
off_reg = (wt_xm_header_end_reg - (reg_addr_size * 2));
type_reg = (wt_xm_header_end_reg - (reg_addr_size * 3));
if (is_gpio) {
len_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
off_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
type_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
if (is_rise) {
len_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
off_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
type_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
}
}
ret = regmap_read(regmap, off_reg, &last_offset);
if (ret) {
dev_err(cs40l2x->dev,
"Failed to read last offset 0x%08X: %d\n",
off_reg, ret);
goto err_free;
}
data_reg = (cs40l2x->xm_hdr_strt_reg +
(last_offset * reg_addr_size));
} else {
num_ym_registers = ((cs40l2x->wt_ym_header_end_pos +
(CS40L2X_WT_NUM_VIRT_SLOTS *
CS40L2X_WT_HEADER_ENTRY_SIZE)) -
cs40l2x->ym_hdr_strt_pos);
wt_ym_header_end_reg = (cs40l2x->ym_hdr_strt_reg +
num_ym_registers);
len_reg = (wt_ym_header_end_reg - reg_addr_size);
off_reg = (wt_ym_header_end_reg - (reg_addr_size * 2));
type_reg = (wt_ym_header_end_reg - (reg_addr_size * 3));
if (is_gpio) {
len_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
off_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
type_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
if (is_rise) {
len_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
off_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
type_reg -= CS40L2X_WT_HEADER_ENTRY_SIZE;
}
}
ret = regmap_read(regmap, off_reg, &last_offset);
if (ret) {
dev_err(cs40l2x->dev,
"Failed to read last offset 0x%08X: %d\n",
off_reg, ret);
goto err_free;
}
data_reg = (cs40l2x->ym_hdr_strt_reg +
(last_offset * reg_addr_size));
}
size_in_words = (size_in_bytes / CS40L2X_WT_TOTAL_WORD_SIZE);
ret = regmap_write(regmap, len_reg, size_in_words);
if (ret) {
dev_err(cs40l2x->dev,
"Failed to write waveform size 0x%08X: %d\n",
len_reg, ret);
goto err_free;
}
ret = regmap_write(regmap, type_reg, wvfrm_type);
if (ret) {
dev_err(cs40l2x->dev,
"Failed to write waveform type 0x%08X: %d\n",
type_reg, ret);
goto err_free;
}
ret = cs40l2x_raw_write(cs40l2x, data_reg, &raw_waveform_data[0],
size_in_bytes, CS40L2X_MAX_WLEN);
if (ret) {
dev_err(cs40l2x->dev, "Failed to write wt virtual data\n");
goto err_free;
}
if (!is_gpio)
cs40l2x->loaded_virtual_index = index;
else {
if (is_rise)
cs40l2x->loaded_gpio_index[CS40L2X_GPIO_RISE] =
index;
else
cs40l2x->loaded_gpio_index[CS40L2X_GPIO_FALL] =
index;
}
err_free:
devm_kfree(cs40l2x->dev, raw_waveform_data);
return ret;
}
static int cs40l2x_add_waveform_to_virtual_list(
struct cs40l2x_private *cs40l2x,
unsigned int type, unsigned int feature,
unsigned int raw_data_size, char *raw_data)
{
struct cs40l2x_virtual_waveform *virtual_wav;
virtual_wav = devm_kzalloc(cs40l2x->dev,
sizeof(*virtual_wav), GFP_KERNEL);
if (!virtual_wav)
return -ENOMEM;
cs40l2x->num_virtual_waves++;
if (cs40l2x->num_virtual_waves > CS40L2X_WT_MAX_VIRT_WAVS) {
dev_err(cs40l2x->dev,
"Attempt to exceed maximum virtual waveforms limit\n");
cs40l2x->num_virtual_waves--;
return -EINVAL;
}
virtual_wav->index = (((cs40l2x->num_waves -
CS40L2X_WT_NUM_VIRT_SLOTS) +
cs40l2x->num_virtual_waves) - 1);
/* The minus 1 is to account for zero being a valid index */
virtual_wav->is_xm = cs40l2x->xm_append;
virtual_wav->wvfrm_type = type;
virtual_wav->wvfrm_feature = feature;
virtual_wav->data_len = raw_data_size;
memcpy(virtual_wav->data, &raw_data[0], raw_data_size);
list_add(&virtual_wav->list, &cs40l2x->virtual_waveform_head);
return 0;
}
static void cs40l2x_save_waveform_to_ovwr_struct(
struct cs40l2x_private *cs40l2x,
unsigned int wvfrm_type,
unsigned int wvfrm_feature,
unsigned int raw_waveform_size,
char *raw_waveform_data)
{
cs40l2x->ovwr_wav->is_xm = cs40l2x->xm_append;
cs40l2x->ovwr_wav->wvfrm_type = wvfrm_type;
cs40l2x->ovwr_wav->wvfrm_feature = wvfrm_feature;
cs40l2x->ovwr_wav->data_len = raw_waveform_size;
memcpy(cs40l2x->ovwr_wav->data, &raw_waveform_data[0],
raw_waveform_size);
}
static void cs40l2x_calc_num_waves(struct cs40l2x_private *cs40l2x)
{
unsigned int wt_file_type;
unsigned int block_length;
unsigned int pos;
int i;
cs40l2x->num_xm_wavs = 0;
cs40l2x->num_ym_wavs = 0;
if (cs40l2x->xm_hdr_strt_pos > 0) {
block_length = cs40l2x->wt_xm_size -
cs40l2x->xm_hdr_strt_pos;
pos = cs40l2x->xm_hdr_strt_pos;
for (i = 0; i < block_length - CS40L2X_WT_HEADER_ENTRY_SIZE;
i += CS40L2X_WT_HEADER_ENTRY_SIZE) {
wt_file_type = (cs40l2x->pbq_fw_raw_wt[pos] << 24)
+ (cs40l2x->pbq_fw_raw_wt[pos + 1] << 16)
+ (cs40l2x->pbq_fw_raw_wt[pos + 2] << 8)
+ (cs40l2x->pbq_fw_raw_wt[pos + 3]);
if (wt_file_type != CS40L2X_WT_TERMINATOR)
cs40l2x->num_xm_wavs++;
else
break;
pos += CS40L2X_WT_HEADER_ENTRY_SIZE;
}
}
if (cs40l2x->ym_hdr_strt_pos > 0) {
block_length = cs40l2x->wt_ym_size -
(cs40l2x->ym_hdr_strt_pos -
cs40l2x->wt_xm_size);
pos = cs40l2x->ym_hdr_strt_pos;
for (i = 0; i < block_length - CS40L2X_WT_HEADER_ENTRY_SIZE;
i += CS40L2X_WT_HEADER_ENTRY_SIZE) {
wt_file_type = (cs40l2x->pbq_fw_raw_wt[pos] << 24)
+ (cs40l2x->pbq_fw_raw_wt[pos + 1] << 16)
+ (cs40l2x->pbq_fw_raw_wt[pos + 2] << 8)
+ (cs40l2x->pbq_fw_raw_wt[pos + 3]);
if (wt_file_type != CS40L2X_WT_TERMINATOR)
cs40l2x->num_ym_wavs++;
else
break;
pos += CS40L2X_WT_HEADER_ENTRY_SIZE;
}
}
cs40l2x->num_waves = cs40l2x->num_xm_wavs + cs40l2x->num_ym_wavs;
}
static int cs40l2x_add_wt_slots(struct cs40l2x_private *cs40l2x,
unsigned int *is_xm)
{
unsigned int wt_open_bytes = CS40L2X_PACKED_BYTES_MAX +
(CS40L2X_WT_HEADER_ENTRY_SIZE * CS40L2X_WT_NUM_VIRT_SLOTS);
unsigned int comp_size;
int no_space = -ENOSPC;
int ret = 0;
while (no_space) {
no_space = cs40l2x_check_wt_open_space(cs40l2x, wt_open_bytes);
if (no_space) {
wt_open_bytes = wt_open_bytes -
CS40L2X_PBQ_FW_BYTES_MIN;
if (wt_open_bytes < CS40L2X_PBQ_FW_BYTES_MIN) {
dev_err(cs40l2x->dev,
"No space in wt for virtual wvfrms\n");
return -ENOSPC;
}
}
}
dev_dbg(cs40l2x->dev,
"Total size of all virtual slots: %d bytes\n",
wt_open_bytes);
cs40l2x_calc_num_waves(cs40l2x);
cs40l2x->virtual_slot_index = ((cs40l2x->num_waves +
CS40L2X_WT_NUM_VIRT_SLOTS) - 1);
cs40l2x->virtual_gpio1_fall_slot =
cs40l2x->virtual_slot_index - 1;
cs40l2x->virtual_gpio1_rise_slot =
cs40l2x->virtual_gpio1_fall_slot - 1;
comp_size = (wt_open_bytes -
(CS40L2X_WT_HEADER_ENTRY_SIZE * CS40L2X_WT_NUM_VIRT_SLOTS));
cs40l2x->comp_bytes = comp_size;
cs40l2x->display_pwle_segs =
(((cs40l2x->comp_bytes / CS40L2X_WT_NUM_VIRT_SLOTS) -
CS40L2X_PWLE_NON_SEG_BYTES) / CS40L2X_PWLE_MAX_SEG_BYTES);
cs40l2x->wvfrm_lengths_size = (cs40l2x->num_waves * 2);
ret = cs40l2x_create_wvfrm_len_type_pairs(cs40l2x);
if (ret)
return ret;
if (cs40l2x->xm_append) {
*is_xm = 1;
ret = cs40l2x_update_existing_block(cs40l2x, comp_size, true);
} else {
*is_xm = 0;
if (cs40l2x->create_ym)
ret = cs40l2x_create_block(cs40l2x, comp_size);
else
ret = cs40l2x_update_existing_block(cs40l2x,
comp_size, false);
}
return ret;
}
static int cs40l2x_convert_and_save_comp_data(struct cs40l2x_private *cs40l2x,
bool over_write)
{
unsigned int comp_size;
char *raw_composite_data;
int ret = 0;
raw_composite_data = kzalloc(CS40L2X_SINGLE_PACKED_MAX, GFP_KERNEL);
if (!raw_composite_data)
return -ENOMEM;
comp_size = cs40l2x_write_comp(cs40l2x, raw_composite_data,
CS40L2X_SINGLE_PACKED_MAX,
&cs40l2x->pbq_comp);
if (comp_size < 0) {
ret = comp_size;
goto err_free;
}
if (comp_size > (cs40l2x->comp_bytes / CS40L2X_WT_NUM_VIRT_SLOTS)) {
dev_err(cs40l2x->dev, "Waveform size exceeds available space\n");
ret = -ENOSPC;
goto err_free;
}
if (over_write)
cs40l2x_save_waveform_to_ovwr_struct(cs40l2x,
CS40L2X_WT_TYPE_10_COMP_FILE, 0, comp_size,
raw_composite_data);
else
ret = cs40l2x_add_waveform_to_virtual_list(cs40l2x,
CS40L2X_WT_TYPE_10_COMP_FILE, 0,
comp_size, raw_composite_data);
err_free:
kfree(raw_composite_data);
return ret;
}
static ssize_t cs40l2x_cp_trigger_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int index;
mutex_lock(&cs40l2x->lock);
index = cs40l2x->cp_trigger_index;
if (cs40l2x->cp_trigger_index == cs40l2x->virtual_slot_index)
index = cs40l2x->loaded_virtual_index;
mutex_unlock(&cs40l2x->lock);
return snprintf(buf, PAGE_SIZE, "%d\n", index);
}
static ssize_t cs40l2x_cp_trigger_index_impl(struct cs40l2x_private *cs40l2x,
unsigned int index)
{
bool gpio_pol, gpio_rise = false;
int ret = 0;
unsigned int reg;
unsigned int gpio_index = CS40L2X_GPIO_FALL;
unsigned int gpio_slot = cs40l2x->virtual_gpio1_fall_slot;
if ((index == CS40L2X_INDEX_PBQ_SAVE) ||
(index == CS40L2X_INDEX_OVWR_SAVE) ||
(index == CS40L2X_INDEX_GP1F_OVWR) ||
(index == CS40L2X_INDEX_GP1R_OVWR)) {
if (!cs40l2x->virtual_bin) {
dev_err(cs40l2x->dev, "Virtual slot not enabled.\n");
return -EINVAL;
}
if (!cs40l2x->queue_stored) {
dev_err(cs40l2x->dev,
"Empty wav data, save composite or PWLE first.\n");
return -EINVAL;
}
#ifdef CONFIG_ANDROID_TIMED_OUTPUT
cs40l2x->safe_save_state = true;
/* Bypass safe_save_state check for Timed Output */
#else
if (!cs40l2x->safe_save_state) {
dev_err(cs40l2x->dev, "Save attempted during vibe, try again.\n");
return -EINVAL;
}
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
cs40l2x->virtual_stored = false;
}
switch (index) {
case CS40L2X_INDEX_PBQ_SAVE:
ret = cs40l2x_convert_and_save_comp_data(cs40l2x, false);
if (ret)
dev_err(cs40l2x->dev, "Unable to save virtual waveform.\n");
/* After save or save attempt, reset flag */
cs40l2x->queue_stored = false;
break;
case CS40L2X_INDEX_OVWR_SAVE:
if (cs40l2x->last_type_entered ==
CS40L2X_WT_TYPE_10_COMP_FILE) {
ret = cs40l2x_convert_and_save_comp_data(cs40l2x, true);
if (ret)
dev_err(cs40l2x->dev, "Unable to convert waveform.\n");
}
ret = cs40l2x_write_virtual_waveform(cs40l2x, index,
false, false, true);
if (ret)
dev_err(cs40l2x->dev, "Unable to write waveform.\n");
index = cs40l2x->virtual_slot_index;
/* After save or save attempt, reset flag */
cs40l2x->queue_stored = false;
break;
case CS40L2X_INDEX_GP1R_OVWR:
gpio_rise = true;
gpio_index = CS40L2X_GPIO_RISE;
gpio_slot = cs40l2x->virtual_gpio1_rise_slot;
fallthrough;
case CS40L2X_INDEX_GP1F_OVWR:
if (cs40l2x->last_type_entered ==
CS40L2X_WT_TYPE_10_COMP_FILE) {
ret = cs40l2x_convert_and_save_comp_data(cs40l2x, true);
if (ret) {
dev_err(cs40l2x->dev, "Unable to convert waveform.\n");
cs40l2x->queue_stored = false;
break;
}
}
ret = cs40l2x_write_virtual_waveform(cs40l2x, index,
true, gpio_rise, true);
if (ret) {
dev_err(cs40l2x->dev, "Unable to write waveform.\n");
cs40l2x->queue_stored = false;
break;
}
cs40l2x->virtual_gpio_index[gpio_index] = index;
gpio_pol = cs40l2x->pdata.gpio_indv_pol &
(1 << (CS40L2X_INDEXBUTTONPRESS1 >> 2));
reg = cs40l2x_dsp_reg(cs40l2x,
gpio_pol ^ gpio_rise ?
"INDEXBUTTONPRESS" :
"INDEXBUTTONRELEASE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id);
if (!reg) {
dev_err(cs40l2x->dev, "Failed to find control.\n");
cs40l2x->queue_stored = false;
break;
}
ret = regmap_write(cs40l2x->regmap, reg, gpio_slot);
if (ret) {
dev_err(cs40l2x->dev, "Unable to set GPIO1 index.\n");
cs40l2x->queue_stored = false;
break;
}
ret = cs40l2x_dsp_cache(cs40l2x, reg, gpio_slot);
if (ret)
dev_err(cs40l2x->dev, "GPIO1 index cache failed.\n");
/* After save or save attempt, reset queue_stored flag */
cs40l2x->queue_stored = false;
break;
case CS40L2X_INDEX_QEST:
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG) {
ret = -EPERM;
break;
}
fallthrough;
case CS40L2X_INDEX_DIAG:
if (cs40l2x->fw_desc->id == cs40l2x->fw_id_remap)
ret = cs40l2x_firmware_swap(cs40l2x,
CS40L2X_FW_ID_CAL);
break;
case CS40L2X_INDEX_PEAK:
case CS40L2X_INDEX_PBQ:
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL) {
if (cs40l2x->cal_disabled_owt) {
cs40l2x->open_wt_enable = true;
cs40l2x->cal_disabled_owt = false;
}
ret = cs40l2x_firmware_swap(cs40l2x,
cs40l2x->fw_id_remap);
}
break;
case CS40L2X_INDEX_IDLE:
ret = -EINVAL;
break;
default:
if (!cs40l2x->virtual_bin) {
if ((index & CS40L2X_INDEX_MASK) >=
cs40l2x->num_waves) {
ret = -EINVAL;
break;
}
} else {
if ((index & CS40L2X_INDEX_MASK) >=
(cs40l2x->num_waves -
CS40L2X_WT_NUM_VIRT_SLOTS)) {
if ((index & CS40L2X_INDEX_MASK) >=
((cs40l2x->num_waves +
cs40l2x->num_virtual_waves) -
CS40L2X_WT_NUM_VIRT_SLOTS)) {
ret = -EINVAL;
break;
}
if (index != cs40l2x->loaded_virtual_index)
cs40l2x_write_virtual_waveform(cs40l2x,
index, false, false, false);
/* else virtual waveform already loaded */
index = cs40l2x->virtual_slot_index;
}
}
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL) {
if (cs40l2x->cal_disabled_owt) {
cs40l2x->open_wt_enable = true;
cs40l2x->cal_disabled_owt = false;
}
ret = cs40l2x_firmware_swap(cs40l2x,
cs40l2x->fw_id_remap);
}
}
if (ret)
goto err_exit;
cs40l2x->cp_trigger_index = index;
err_exit:
if (!cs40l2x->virtual_stored)
cs40l2x->virtual_stored = true;
return ret;
}
static ssize_t cs40l2x_cp_trigger_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int index;
int ret;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_cp_trigger_index_impl(cs40l2x, index);
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
if (ret)
return ret;
return count;
}
static ssize_t cs40l2x_cp_trigger_queue_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct wt_type10_comp_section *section = cs40l2x->pbq_comp.sections;
char *pbq_str;
int i, len = 0;
if (!cs40l2x->pbq_str_size) {
dev_err(dev, "PBQ string is not set\n");
return -EPERM;
}
pbq_str = kzalloc(cs40l2x->pbq_str_size + 1, GFP_KERNEL);
if (!pbq_str)
return -ENOMEM;
mutex_lock(&cs40l2x->lock);
for (i = 0; i < cs40l2x->pbq_comp.nsections; i++, section++) {
if (section->repeat == WT_REPEAT_LOOP_MARKER)
len += snprintf(pbq_str + len, PAGE_SIZE - len, "!!, ");
if (section->amplitude)
len += snprintf(pbq_str + len, PAGE_SIZE - len, "%d.%d, ",
section->index, section->amplitude);
if (section->delay)
len += snprintf(pbq_str + len, PAGE_SIZE - len, "%d, ",
section->delay);
if (section->repeat && section->repeat != WT_REPEAT_LOOP_MARKER)
len += snprintf(pbq_str + len, PAGE_SIZE - len, "%d!!, ",
section->repeat);
}
switch (cs40l2x->pbq_comp.repeat) {
case WT_REPEAT_LOOP_MARKER:
len += snprintf(pbq_str + len, PAGE_SIZE - len, "~\n");
break;
case 0:
len -= 2; // Remove ", " from end of string
len += snprintf(pbq_str + len, PAGE_SIZE - len, "\n");
break;
default:
len += snprintf(pbq_str + len, PAGE_SIZE - len, "%d!\n",
cs40l2x->pbq_comp.repeat);
}
len = strscpy(buf, pbq_str, PAGE_SIZE);
if (len == -E2BIG)
dev_err(dev, "String too large for buffer\n");
mutex_unlock(&cs40l2x->lock);
kfree(pbq_str);
return len;
}
static int cs40l2x_comp_finalise_section(struct cs40l2x_private *cs40l2x)
{
struct wt_type10_comp *comp = &cs40l2x->pbq_comp;
struct wt_type10_comp_section *sec = &comp->sections[comp->nsections];
unsigned int lindex = sec->index * 2;
int slen = 0;
if (sec->index) {
switch (cs40l2x->wvfrm_lengths[lindex]) {
case CS40L2X_WT_TYPE_8_PCM_FILE:
case CS40L2X_WT_TYPE_9_VAR_FILE:
case CS40L2X_WT_TYPE_12_PWLE_FILE:
break;
default:
dev_err(cs40l2x->dev, "Invalid PBQ waveform\n");
return -EINVAL;
}
slen = cs40l2x->wvfrm_lengths[lindex + 1];
if (slen & WT_WAVELEN_INDEFINITE) {
if (!(sec->flags & WT_T10_FLAG_DURATION)) {
dev_err(cs40l2x->dev, "Indefinite PBQ entry needs duration\n");
return -EINVAL;
}
slen = WT_WAVELEN_MAX;
} else {
slen &= WT_WAVELEN_MAX;
}
}
slen += sec->delay * 8;
if (sec->flags & WT_T10_FLAG_DURATION)
slen = min(slen, 2 * sec->duration);
comp->wlength += slen;
comp->nsections++;
if (comp->nsections == WT_MAX_SECTIONS)
return -E2BIG;
return slen;
}
static ssize_t cs40l2x_cp_trigger_queue_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct i2c_client *i2c_client = to_i2c_client(cs40l2x->dev);
struct wt_type10_comp *comp = &cs40l2x->pbq_comp;
struct wt_type10_comp_section *section;
char *pbq_str, *token, *cur;
unsigned int num_waves = cs40l2x->num_waves;
unsigned int index, amp, val;
unsigned int inner_samples = 0;
bool inner_loop = false;
int ret;
if (count >= PAGE_SIZE) {
dev_err(dev, "Trigger queue string too large\n");
return -E2BIG;
}
pbq_str = kstrndup(buf, count, GFP_KERNEL);
if (!pbq_str)
return -ENOMEM;
cs40l2x->pbq_str_size = count;
disable_irq(i2c_client->irq);
mutex_lock(&cs40l2x->lock);
cs40l2x->queue_stored = false;
if (cs40l2x->virtual_bin)
num_waves = cs40l2x->num_waves - CS40L2X_WT_NUM_VIRT_SLOTS;
if (!cs40l2x->virtual_stored) {
dev_err(dev, "Unsafe condition encountered.\n");
ret = -EINVAL;
goto err_mutex;
}
memset(comp, 0, sizeof(*comp));
section = comp->sections;
cur = pbq_str;
while ((token = strsep(&cur, ","))) {
token = strim(token);
/* loop specifier */
if (!strcmp(token, "~")) {
if (comp->repeat) {
dev_err(cs40l2x->dev, "Duplicate outer loop specifier\n");
ret = -EINVAL;
goto err_mutex;
}
comp->repeat = WT_REPEAT_LOOP_MARKER;
/* inner loop start: "!!" */
} else if (!strcmp(token, "!!")) {
if (inner_loop) {
dev_err(cs40l2x->dev, "Nested inner loop specifier\n");
ret = -EINVAL;
goto err_mutex;
}
if (section->amplitude || section->delay) {
ret = cs40l2x_comp_finalise_section(cs40l2x);
if (ret < 0)
goto err_mutex;
section++;
}
section->repeat = WT_REPEAT_LOOP_MARKER;
inner_loop = true;
/* inner loop stop: "n!!" */
} else if (strstr(token, "!!")) {
if (!inner_loop) {
dev_err(cs40l2x->dev, "Inner loop with no start\n");
ret = -EINVAL;
goto err_mutex;
}
ret = kstrtou32(strsep(&token, "!"), 10, &val);
if (ret) {
dev_err(cs40l2x->dev,
"Failed to parse inner loop specifier: %d\n",
ret);
goto err_mutex;
}
section->repeat = val;
ret = cs40l2x_comp_finalise_section(cs40l2x);
if (ret < 0)
goto err_mutex;
section++;
if (inner_loop)
comp->wlength += (inner_samples + ret) * val;
inner_loop = false;
inner_samples = 0;
/* repetition specifier */
} else if (strchr(token, '!')) {
if (comp->repeat) {
dev_err(cs40l2x->dev, "Duplicate outer loop specifier\n");
ret = -EINVAL;
goto err_mutex;
}
ret = kstrtou32(strsep(&token, "!"), 10, &val);
if (ret) {
dev_err(cs40l2x->dev,
"Failed to parse outer loop specifier: %d\n",
ret);
goto err_mutex;
}
comp->repeat = val;
/* waveform specifier */
} else if (strchr(token, '.')) {
ret = sscanf(token, "%u.%u.%u", &index, &amp, &val);
if (ret < 2) {
dev_err(cs40l2x->dev,
"Failed to parse waveform: %d\n", ret);
ret = -EINVAL;
goto err_mutex;
}
if ((ret == 2 && index == 0) || index >= num_waves) {
dev_err(cs40l2x->dev, "Invalid waveform index\n");
ret = -EINVAL;
goto err_mutex;
}
if (amp == 0 || amp > CS40L2X_PBQ_SCALE_MAX) {
dev_err(cs40l2x->dev, "Invalid waveform amplitude\n");
ret = -EINVAL;
goto err_mutex;
}
if (ret == 3) {
if (!cs40l2x->comp_dur_min_fw) {
dev_err(cs40l2x->dev, "Composite duration not supported by firmware\n");
ret = -EINVAL;
goto err_mutex;
}
if (val != CS40L2X_PWLE_INDEF_TIME_VAL) {
if (val > CS40L2X_PWLE_MAX_TIME_VAL) {
dev_err(cs40l2x->dev, "Invalid duration: 0 to 16383ms, or 65535\n");
ret = -EINVAL;
goto err_mutex;
}
val *= 4; /* Time stored in 1/4 ms */
}
section->flags |= WT_T10_FLAG_DURATION;
} else {
val = 0;
}
if (section->amplitude || section->delay) {
ret = cs40l2x_comp_finalise_section(cs40l2x);
if (ret < 0)
goto err_mutex;
section++;
if (inner_loop)
inner_samples += ret;
}
section->index = index;
section->amplitude = amp;
section->duration = val;
/* delay specifier */
} else {
ret = kstrtou32(token, 10, &val);
if (ret) {
dev_err(cs40l2x->dev,
"Failed to parse duration: %d\n", ret);
goto err_mutex;
}
if (val > CS40L2X_PBQ_DELAY_MAX) {
dev_err(cs40l2x->dev, "Delay too long\n");
ret = -EINVAL;
goto err_mutex;
}
if (section->delay) {
ret = cs40l2x_comp_finalise_section(cs40l2x);
if (ret < 0)
goto err_mutex;
section++;
if (inner_loop)
inner_samples += ret;
}
section->delay = val;
}
}
if (section->amplitude || section->delay) {
ret = cs40l2x_comp_finalise_section(cs40l2x);
if (ret < 0)
goto err_mutex;
}
if (comp->repeat == WT_REPEAT_LOOP_MARKER) {
comp->wlength = WT_WAVELEN_INDEFINITE;
} else {
comp->wlength *= comp->repeat + 1;
clamp_t(unsigned int, comp->wlength, 0, WT_WAVELEN_MAX);
}
comp->wlength |= WT_WAVELEN_CALCULATED;
cs40l2x->last_type_entered = CS40L2X_WT_TYPE_10_COMP_FILE;
cs40l2x->queue_stored = true;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
kfree(pbq_str);
enable_irq(i2c_client->irq);
return ret;
}
static int cs40l2x_save_packed_pwle_data(struct cs40l2x_private *cs40l2x,
struct wt_type12_pwle *pwle, unsigned int feature, bool save)
{
unsigned int zero_pad_size;
char *zero_pad_data;
int ret;
zero_pad_data = kzalloc(CS40L2X_MAX_WLEN, GFP_KERNEL);
if (!zero_pad_data)
return -ENOMEM;
ret = cs40l2x_write_pwle(cs40l2x, zero_pad_data,
CS40L2X_MAX_WLEN, pwle);
if (ret > (cs40l2x->comp_bytes / CS40L2X_WT_NUM_VIRT_SLOTS)) {
dev_err(cs40l2x->dev, "PWLE size exceeds available space\n");
kfree(zero_pad_data);
return -ENOSPC;
}
zero_pad_size = ret;
if (save) {
ret = cs40l2x_add_waveform_to_virtual_list(cs40l2x,
CS40L2X_WT_TYPE_12_PWLE_FILE, feature, zero_pad_size,
zero_pad_data);
if (!ret)
cs40l2x->num_virtual_pwle_waves++;
} else {
cs40l2x_save_waveform_to_ovwr_struct(cs40l2x,
CS40L2X_WT_TYPE_12_PWLE_FILE, feature, zero_pad_size,
zero_pad_data);
ret = 0;
}
kfree(zero_pad_data);
return ret;
}
static int cs40l2x_pwle_repeat_entry(struct cs40l2x_private *cs40l2x,
char *token, struct wt_type12_pwle *pwle)
{
unsigned int val;
int ret;
ret = kstrtou32(token, 10, &val);
if (ret)
return ret;
if (val > CS40L2X_PWLE_MAX_RP_VAL) {
dev_err(cs40l2x->dev, "Valid Repeat: 0 to 255\n");
return -EINVAL;
}
pwle->repeat = val;
return ret;
}
static int cs40l2x_parse_float(char *frac, int *result, int nfracdigits,
int min, int max)
{
char *inte, convert[] = "000000000";
int ninte, nfrac = 0;
int ret;
if (strlen(frac) > strlen(convert))
return -EOVERFLOW;
inte = strsep(&frac, ".");
ninte = strlen(inte);
if (frac)
nfrac = strlen(frac);
memcpy(convert, inte, ninte);
memcpy(convert + ninte, frac, min(nfrac, nfracdigits));
convert[ninte + nfracdigits] = 0;
ret = kstrtoint(convert, 10, result);
if (ret)
return ret;
if (*result < min || *result > max)
return -ERANGE;
return 0;
}
static int cs40l2x_pwle_wait_time_entry(struct cs40l2x_private *cs40l2x,
char *token, struct wt_type12_pwle *pwle)
{
int val, ret;
/* Valid values, as per spec, 0mS - 1023.75mS */
ret = cs40l2x_parse_float(token, &val, 2, 0, 102375);
if (ret) {
dev_err(cs40l2x->dev, "Failed to parse wait time: %d\n", ret);
return ret;
}
pwle->wait = val / (100 / 4);
pwle->wlength += pwle->wait;
return ret;
}
static int cs40l2x_pwle_time_entry(struct cs40l2x_private *cs40l2x, char *token,
struct wt_type12_pwle *pwle,
struct wt_type12_pwle_section *section,
bool *indef)
{
int val, ret;
/* Valid values, as per spec, 0mS - 16383.5mS, 16383.75mS = infinite */
ret = cs40l2x_parse_float(token, &val, 2, 0, 1638375);
if (ret) {
dev_err(cs40l2x->dev, "Failed to parse time: %d\n", ret);
return ret;
}
section->time = val / (100 / 4);
if (val == CS40L2X_PWLE_INDEF_TIME_VAL)
*indef = true;
else
pwle->wlength += section->time;
return ret;
}
static int cs40l2x_pwle_level_entry(struct cs40l2x_private *cs40l2x, char *token,
struct wt_type12_pwle_section *section)
{
int val, ret;
/* Valid values, as per spec, -1 - 0.9995118 */
ret = cs40l2x_parse_float(token, &val, 7, -10000000, 9995118);
if (ret) {
dev_err(cs40l2x->dev, "Failed to parse level: %d\n", ret);
return ret;
}
section->level = val / (10000000 / 2048);
return ret;
}
static int cs40l2x_pwle_frequency_entry(struct cs40l2x_private *cs40l2x,
char *token,
struct wt_type12_pwle_section *section)
{
int val, ret;
/* Valid values, as per spec, 50.125Hz - 561.875Hz */
int min = 50125, max = 561875;
if (cs40l2x->ext_freq_min_fw) {
/* Valid values, as per spec, 0.25Hz - 1023.75Hz */
min = 250;
max = 1023750;
}
ret = cs40l2x_parse_float(token, &val, 3, min, max);
if (ret) {
dev_err(cs40l2x->dev, "Failed to parse frequency: %d\n", ret);
return ret;
}
if (cs40l2x->ext_freq_min_fw) {
section->frequency = (val / (1000 / 4));
section->flags |= WT_T12_FLAG_EXT_FREQ;
} else {
section->frequency = (val / (1000 / 8)) - 400;
}
return ret;
}
static int cs40l2x_pwle_vb_target_entry(struct cs40l2x_private *cs40l2x,
char *token,
struct wt_type12_pwle_section *section)
{
int val, ret;
/*
* We don't pass a scale value as we will scale locally, valid values,
* as per spec, are 0 - 1.
*/
ret = cs40l2x_parse_float(token, &val, 6, 0, 1000000);
if (ret) {
dev_err(cs40l2x->dev, "Failed to parse frequency: %d\n", ret);
return ret;
}
/* Approximation to scaling to 999999/0x7fffff without overflowing */
val = (val * 1770) / 211;
clamp(val, 0, 0x7FFFFF);
section->vbtarget = val;
return ret;
}
static ssize_t cs40l2x_pwle_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct i2c_client *i2c_client = to_i2c_client(cs40l2x->dev);
struct wt_type12_pwle *pwle;
struct wt_type12_pwle_section *section;
char *pwle_str, *cur, *token, *type;
unsigned int num_vals = 0, num_segs = 0, feature = 0;
unsigned int val;
bool indef = false, save_pwle = false;
bool t = false, l = false, f = false, c = false, b = false;
bool a = false, v = false;
int ret;
if (count > CS40L2X_MAX_WLEN - 1) {
dev_err(dev, "PWLE string too large: %lu\n", count);
return -E2BIG;
}
pwle_str = kzalloc(count + 1, GFP_KERNEL);
if (!pwle_str)
return -ENOMEM;
pwle = kzalloc(sizeof(*pwle), GFP_KERNEL);
if (!pwle) {
kfree(pwle_str);
return -ENOMEM;
}
disable_irq(i2c_client->irq);
mutex_lock(&cs40l2x->lock);
cs40l2x->queue_stored = false;
if (!cs40l2x->virtual_stored) {
dev_err(dev, "Unsafe condition encountered.\n");
ret = -EINVAL;
goto err_exit;
}
section = pwle->sections;
ret = strscpy(pwle_str, buf, count + 1);
if (ret == -E2BIG)
goto err_exit;
cur = pwle_str;
while ((token = strsep(&cur, ","))) {
token = strim(token);
if (num_vals >= CS40L2X_PWLE_TOTAL_VALS) {
ret = -E2BIG;
goto err_exit;
}
type = strsep(&token, ":");
if (!type || !token) {
dev_err(cs40l2x->dev,
"Malformed PWLE. : not found\n");
ret = -EINVAL;
goto err_exit;
}
token = strim(token);
if (type[0] == 'S') {
if (num_vals != 0) {
dev_err(cs40l2x->dev,
"Malformed PWLE, missing Save entry\n");
ret = -EINVAL;
goto err_exit;
}
ret = kstrtou32(token, 10, &val);
if (ret)
goto err_exit;
if (val > 1) {
dev_err(cs40l2x->dev, "Valid Save: 0 or 1\n");
ret = -EINVAL;
goto err_exit;
}
if (val)
save_pwle = true;
} else if (!strncmp(type, "WF", 2)) {
if (num_vals != 1) {
dev_err(cs40l2x->dev,
"Malformed PWLE, missing Feature entry\n");
ret = -EINVAL;
goto err_exit;
}
ret = kstrtou32(token, 10, &val);
if (ret)
goto err_exit;
if (val > CS40L2X_PWLE_MAX_WVFRM_FEAT ||
(val % 4) != 0) {
dev_err(cs40l2x->dev,
"Valid Waveform Feature: 0, 4, 8, 12\n");
ret = -EINVAL;
goto err_exit;
}
feature = val << CS40L2X_PWLE_WVFRM_FT_SHFT;
} else if (!strncmp(type, "RP", 2)) {
if (num_vals != 2) {
dev_err(cs40l2x->dev,
"Malformed PWLE, missing Repeat entry\n");
ret = -EINVAL;
goto err_exit;
}
ret = cs40l2x_pwle_repeat_entry(cs40l2x, token, pwle);
if (ret)
goto err_exit;
} else if (!strncmp(type, "WT", 2)) {
if (num_vals != 3) {
dev_err(cs40l2x->dev,
"Malformed PWLE, WaitTime follows Repeat\n");
ret = -EINVAL;
goto err_exit;
}
ret = cs40l2x_pwle_wait_time_entry(cs40l2x, token, pwle);
if (ret)
goto err_exit;
} else if (type[0] == 'T') {
if (num_vals > 4) {
/* Verify complete previous segment */
if (!t || !l || !f || !c || !b || !a || !v) {
dev_err(cs40l2x->dev,
"Malformed PWLE. Missing entry in seg %d\n",
(num_segs - 1));
ret = -EINVAL;
goto err_exit;
}
t = false;
l = false;
f = false;
c = false;
b = false;
a = false;
v = false;
}
ret = cs40l2x_pwle_time_entry(cs40l2x, token, pwle,
section, &indef);
if (ret)
goto err_exit;
t = true;
} else if (type[0] == 'L') {
ret = cs40l2x_pwle_level_entry(cs40l2x, token, section);
if (ret)
goto err_exit;
l = true;
} else if (type[0] == 'F') {
ret = cs40l2x_pwle_frequency_entry(cs40l2x, token,
section);
if (ret)
goto err_exit;
f = true;
} else if (type[0] == 'C') {
ret = kstrtou32(token, 10, &val);
if (ret)
goto err_exit;
if (val > 1) {
dev_err(cs40l2x->dev,
"Valid Chirp: 0 or 1\n");
ret = -EINVAL;
goto err_exit;
}
if (val)
section->flags |= WT_T12_FLAG_CHIRP;
c = true;
} else if (type[0] == 'B') {
ret = kstrtou32(token, 10, &val);
if (ret)
goto err_exit;
if (val > 1) {
dev_err(cs40l2x->dev,
"Valid Braking: 0 or 1\n");
ret = -EINVAL;
goto err_exit;
}
if (val)
section->flags |= WT_T12_FLAG_BRAKE;
b = true;
} else if (!strncmp(type, "AR", 2)) {
ret = kstrtou32(token, 10, &val);
if (ret)
goto err_exit;
if (val > 1) {
dev_err(cs40l2x->dev,
"Valid Amplitude Regulation: 0 or 1\n");
ret = -EINVAL;
goto err_exit;
}
if (val)
section->flags |= WT_T12_FLAG_AMP_REG;
a = true;
} else if (type[0] == 'V') {
if (section->flags & WT_T12_FLAG_AMP_REG) {
ret = cs40l2x_pwle_vb_target_entry(cs40l2x,
token,
section);
if (ret)
goto err_exit;
}
v = true;
num_segs++;
section++;
}
num_vals++;
}
/* Verify last segment was complete */
if (!t || !l || !f || !c || !b || !a || !v) {
dev_err(cs40l2x->dev,
"Malformed PWLE. Missing entry in seg %d\n",
(num_segs - 1));
ret = -EINVAL;
goto err_exit;
}
pwle->nsections = num_segs;
ret = strscpy_pad(cs40l2x->pwle_str, buf, CS40L2X_MAX_WLEN);
if (ret == -E2BIG) {
goto err_exit;
}
cs40l2x->pwle_str_size = count;
pwle->wlength *= pwle->repeat + 1;
/* Firmware doesn't count the last wait since it is just dead time */
pwle->wlength -= pwle->wait;
/* Convert from 1/4mS's to samples at 8kHz for waveform length */
pwle->wlength *= 2;
if (indef)
pwle->wlength |= WT_WAVELEN_INDEFINITE;
pwle->wlength |= WT_WAVELEN_CALCULATED;
ret = cs40l2x_save_packed_pwle_data(cs40l2x, pwle, feature, save_pwle);
if (ret) {
dev_err(cs40l2x->dev,
"Malformed PWLE. No segments found.\n");
ret = -EINVAL;
goto err_exit;
}
ret = count;
if (!save_pwle)
cs40l2x->last_type_entered = CS40L2X_WT_TYPE_12_PWLE_FILE;
cs40l2x->queue_stored = true;
err_exit:
mutex_unlock(&cs40l2x->lock);
kfree(pwle);
kfree(pwle_str);
enable_irq(i2c_client->irq);
return ret;
}
static ssize_t cs40l2x_pwle_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
ssize_t len = 0;
int i;
mutex_lock(&cs40l2x->lock);
for (i = 0; i < cs40l2x->pwle_str_size; i++)
len += snprintf(buf + len, PAGE_SIZE - len, "%c",
cs40l2x->pwle_str[i]);
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
mutex_unlock(&cs40l2x->lock);
return len;
}
static ssize_t cs40l2x_composite_indexes_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct cs40l2x_virtual_waveform *virtual_wav;
ssize_t len = 0;
int count = 0;
mutex_lock(&cs40l2x->lock);
list_for_each_entry_reverse(virtual_wav,
&cs40l2x->virtual_waveform_head, list) {
if (virtual_wav->wvfrm_type != CS40L2X_WT_TYPE_10_COMP_FILE)
continue;
count++;
len += snprintf(buf + len, PAGE_SIZE - len, "%d",
virtual_wav->index);
if (count != (cs40l2x->num_virtual_waves -
cs40l2x->num_virtual_pwle_waves))
len += snprintf(buf + len, PAGE_SIZE - len, ", ");
}
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
mutex_unlock(&cs40l2x->lock);
return len;
}
static ssize_t cs40l2x_pwle_indexes_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct cs40l2x_virtual_waveform *virtual_wav;
ssize_t len = 0;
int count = 0;
mutex_lock(&cs40l2x->lock);
list_for_each_entry_reverse(virtual_wav,
&cs40l2x->virtual_waveform_head, list) {
if (virtual_wav->wvfrm_type != CS40L2X_WT_TYPE_12_PWLE_FILE)
continue;
count++;
len += snprintf(buf + len, PAGE_SIZE - len, "%d",
virtual_wav->index);
if (count != cs40l2x->num_virtual_pwle_waves)
len += snprintf(buf + len, PAGE_SIZE - len, ", ");
}
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
mutex_unlock(&cs40l2x->lock);
return len;
}
static ssize_t cs40l2x_available_pwle_segs_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int index;
mutex_lock(&cs40l2x->lock);
index = cs40l2x->display_pwle_segs;
mutex_unlock(&cs40l2x->lock);
return snprintf(buf, PAGE_SIZE, "%d\n", index);
}
static unsigned int cs40l2x_dsp_reg(struct cs40l2x_private *cs40l2x,
const char *coeff_name, const unsigned int block_type,
const unsigned int algo_id)
{
struct cs40l2x_coeff_desc *coeff_desc;
list_for_each_entry(coeff_desc, &cs40l2x->coeff_desc_head, list) {
if (strncmp(coeff_desc->name, coeff_name,
CS40L2X_COEFF_NAME_LEN_MAX))
continue;
if (coeff_desc->block_type != block_type)
continue;
if (coeff_desc->parent_id != algo_id)
continue;
return coeff_desc->reg;
}
return 0;
}
static int cs40l2x_dsp_cache(struct cs40l2x_private *cs40l2x,
unsigned int reg, unsigned int val)
{
int i;
for (i = 0; i < cs40l2x->dsp_cache_depth; i++)
if (cs40l2x->dsp_cache[i].reg == reg) {
cs40l2x->dsp_cache[i].val = val;
return 0;
}
if (i == CS40L2X_DSP_CACHE_MAX)
return -E2BIG;
cs40l2x->dsp_cache[cs40l2x->dsp_cache_depth].reg = reg;
cs40l2x->dsp_cache[cs40l2x->dsp_cache_depth++].val = val;
return 0;
}
static int cs40l2x_wseq_add_reg(struct cs40l2x_private *cs40l2x,
unsigned int reg, unsigned int val)
{
if (cs40l2x->wseq_length == CS40L2X_WSEQ_LENGTH_MAX)
return -E2BIG;
cs40l2x->wseq_table[cs40l2x->wseq_length].reg = reg;
cs40l2x->wseq_table[cs40l2x->wseq_length++].val = val;
return 0;
}
static int cs40l2x_wseq_add_seq(struct cs40l2x_private *cs40l2x,
const struct reg_sequence *seq, unsigned int len)
{
int ret, i;
for (i = 0; i < len; i++) {
ret = cs40l2x_wseq_add_reg(cs40l2x, seq[i].reg, seq[i].def);
if (ret)
return ret;
}
return 0;
}
static int cs40l2x_wseq_write(struct cs40l2x_private *cs40l2x, unsigned int pos,
unsigned int reg, unsigned int val)
{
unsigned int wseq_base = cs40l2x_dsp_reg(cs40l2x, "POWERONSEQUENCE",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
int ret;
/* missing write sequencer simply means there is nothing to do here */
if (!wseq_base)
return 0;
/* upper half */
ret = regmap_write(cs40l2x->regmap,
wseq_base + pos * CS40L2X_WSEQ_STRIDE,
((reg & CS40L2X_WSEQ_REG_MASK1)
<< CS40L2X_WSEQ_REG_SHIFTUP) |
((val & CS40L2X_WSEQ_VAL_MASK1)
>> CS40L2X_WSEQ_VAL_SHIFTDN));
if (ret)
return ret;
/* lower half */
return regmap_write(cs40l2x->regmap,
wseq_base + pos * CS40L2X_WSEQ_STRIDE + 4,
val & CS40L2X_WSEQ_VAL_MASK2);
}
static int cs40l2x_wseq_init(struct cs40l2x_private *cs40l2x)
{
unsigned int wseq_base = cs40l2x_dsp_reg(cs40l2x, "POWERONSEQUENCE",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
int ret, i;
if (!wseq_base)
return 0;
for (i = 0; i < cs40l2x->wseq_length; i++) {
ret = cs40l2x_wseq_write(cs40l2x, i,
cs40l2x->wseq_table[i].reg,
cs40l2x->wseq_table[i].val);
if (ret)
return ret;
}
return regmap_write(cs40l2x->regmap,
wseq_base + cs40l2x->wseq_length * CS40L2X_WSEQ_STRIDE,
CS40L2X_WSEQ_LIST_TERM);
}
static int cs40l2x_wseq_replace(struct cs40l2x_private *cs40l2x,
unsigned int reg, unsigned int val)
{
int i;
for (i = 0; i < cs40l2x->wseq_length; i++)
if (cs40l2x->wseq_table[i].reg == reg)
break;
if (i == cs40l2x->wseq_length)
return -EINVAL;
cs40l2x->wseq_table[i].val = val;
return cs40l2x_wseq_write(cs40l2x, i, reg, val);
}
static int cs40l2x_user_ctrl_exec(struct cs40l2x_private *cs40l2x,
unsigned int user_ctrl_cmd, unsigned int user_ctrl_data,
unsigned int *user_ctrl_resp)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int user_ctrl_reg = cs40l2x_dsp_reg(cs40l2x,
"USER_CONTROL_IPDATA",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
int ret;
if (!user_ctrl_reg)
return -EPERM;
ret = regmap_write(regmap, user_ctrl_reg, user_ctrl_data);
if (ret) {
dev_err(dev, "Failed to write user-control data\n");
return ret;
}
ret = cs40l2x_ack_write(cs40l2x, CS40L2X_MBOX_USER_CONTROL,
user_ctrl_cmd, CS40L2X_USER_CTRL_SUCCESS);
if (ret)
return ret;
if (!user_ctrl_resp)
return 0;
return regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "USER_CONTROL_RESPONSE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
user_ctrl_resp);
}
static ssize_t cs40l2x_cp_trigger_duration_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
index = cs40l2x->cp_trigger_index;
switch (cs40l2x->fw_desc->id) {
case CS40L2X_FW_ID_ORIG:
ret = -EPERM;
goto err_mutex;
case CS40L2X_FW_ID_CAL:
if (index != CS40L2X_INDEX_QEST) {
ret = -EINVAL;
goto err_mutex;
}
if (cs40l2x->diag_state < CS40L2X_DIAG_STATE_DONE1) {
ret = -ENODATA;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap,
cs40l2x_dsp_reg(cs40l2x, "TONE_DURATION_MS",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_QEST),
&val);
if (ret)
goto err_mutex;
if (val == CS40L2X_TONE_DURATION_MS_NONE) {
ret = -ENODATA;
goto err_mutex;
}
val *= CS40L2X_QEST_SRATE;
break;
default:
if (index < CS40L2X_INDEX_CLICK_MIN
|| index > CS40L2X_INDEX_CLICK_MAX) {
ret = -EINVAL;
goto err_mutex;
}
ret = cs40l2x_user_ctrl_exec(cs40l2x,
CS40L2X_USER_CTRL_DURATION, index, &val);
if (ret)
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_cp_trigger_q_sub_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_user_ctrl_exec(cs40l2x, CS40L2X_USER_CTRL_Q_INDEX,
cs40l2x->cp_trigger_index, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static int cs40l2x_wait_for_pwrmgt_sts(struct cs40l2x_private *cs40l2x)
{
unsigned int sts;
int i, ret;
for (i = 0; i < CS40L2X_STATUS_RETRIES; i++) {
ret = regmap_read(cs40l2x->regmap, CS40L2X_PWRMGT_STS, &sts);
if (ret)
dev_err(cs40l2x->dev,
"Failed to read PWRMGT_STS: %d\n", ret);
else if (!(sts & CS40L2X_WR_PEND_STS_MASK))
return 0;
}
dev_err(cs40l2x->dev, "Timed out reading PWRMGT_STS\n");
return -ETIMEDOUT;
}
static int cs40l2x_apply_hibernate_errata(struct cs40l2x_private *cs40l2x)
{
int ret;
dev_warn(cs40l2x->dev, "Retry hibernate\n");
cs40l2x_wait_for_pwrmgt_sts(cs40l2x);
ret = regmap_write(cs40l2x->regmap, CS40L2X_WAKESRC_CTL,
(CS40L2X_WKSRC_EN_SDA << CS40L2X_WKSRC_EN_SHIFT) |
(CS40L2X_WKSRC_POL_SDA << CS40L2X_WKSRC_POL_SHIFT));
if (ret)
dev_err(cs40l2x->dev, "Failed to set WAKESRC: %d\n", ret);
cs40l2x_wait_for_pwrmgt_sts(cs40l2x);
ret = regmap_write(cs40l2x->regmap, CS40L2X_WAKESRC_CTL,
CS40L2X_UPDT_WKCTL_MASK |
(CS40L2X_WKSRC_EN_SDA << CS40L2X_WKSRC_EN_SHIFT) |
(CS40L2X_WKSRC_POL_SDA << CS40L2X_WKSRC_POL_SHIFT));
if (ret)
dev_err(cs40l2x->dev, "Failed to enable WAKESRC: %d\n", ret);
cs40l2x_wait_for_pwrmgt_sts(cs40l2x);
/*
* This write may force the device into hibernation before the ACK is
* returned, so ignore the return value.
*/
regmap_write(cs40l2x->regmap, CS40L2X_PWRMGT_CTL,
(1 << CS40L2X_MEM_RDY_SHIFT) |
(1 << CS40L2X_TRIG_HIBER_SHIFT));
return 0;
}
static int cs40l2x_wake_from_hibernate(struct cs40l2x_private *cs40l2x)
{
unsigned int pwr_reg = cs40l2x_dsp_reg(cs40l2x, "POWERSTATE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id);
unsigned int val;
int ret, i;
dev_dbg(cs40l2x->dev, "Attempt wake from hibernate\n");
ret = cs40l2x_ack_write(cs40l2x, CS40L2X_MBOX_POWERCONTROL,
CS40L2X_POWERCONTROL_WAKEUP,
CS40L2X_POWERCONTROL_NONE);
if (ret) {
if (ret == -ETIME)
cs40l2x_apply_hibernate_errata(cs40l2x);
return ret;
}
for (i = 0; i < CS40L2X_STATUS_RETRIES; i++) {
ret = regmap_read(cs40l2x->regmap, pwr_reg, &val);
if (ret) {
dev_err(cs40l2x->dev, "Failed to read POWERSTATE: %d\n",
ret);
return ret;
}
dev_dbg(cs40l2x->dev, "Read POWERSTATE: %d\n", val);
switch (val) {
case CS40L2X_POWERSTATE_ACTIVE:
case CS40L2X_POWERSTATE_STANDBY:
dev_dbg(cs40l2x->dev, "Woke from hibernate\n");
return 0;
case CS40L2X_POWERSTATE_HIBERNATE:
break;
default:
dev_err(cs40l2x->dev, "Invalid POWERSTATE: %x\n", val);
break;
}
usleep_range(5000, 5100);
}
dev_err(cs40l2x->dev, "Timed out waiting for POWERSTATE: %d\n", val);
cs40l2x_apply_hibernate_errata(cs40l2x);
return -ETIMEDOUT;
}
static int cs40l2x_hiber_cmd_send(struct cs40l2x_private *cs40l2x,
unsigned int hiber_cmd)
{
int i;
switch (hiber_cmd) {
case CS40L2X_POWERCONTROL_NONE:
case CS40L2X_POWERCONTROL_FRC_STDBY:
return cs40l2x_ack_write(cs40l2x, CS40L2X_MBOX_POWERCONTROL,
hiber_cmd, CS40L2X_POWERCONTROL_NONE);
case CS40L2X_POWERCONTROL_HIBERNATE:
/*
* The control port is unavailable immediately after this write,
* so don't poll for acknowledgment.
*/
return regmap_write(cs40l2x->regmap, CS40L2X_MBOX_POWERCONTROL,
hiber_cmd);
case CS40L2X_POWERCONTROL_WAKEUP:
/*
* The first several transactions are expected to be NAK'd, so
* retry multiple times in rapid succession.
*/
for (i = 0; i < CS40L2X_WAKEUP_RETRIES; i++) {
if (!cs40l2x_wake_from_hibernate(cs40l2x))
return 0;
usleep_range(1000, 1100);
}
return -ETIMEDOUT;
default:
return -EINVAL;
}
}
static ssize_t cs40l2x_hiber_cmd_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int hiber_cmd;
ret = kstrtou32(buf, 10, &hiber_cmd);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL
|| cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG) {
ret = -EPERM;
goto err_mutex;
}
ret = cs40l2x_hiber_cmd_send(cs40l2x, hiber_cmd);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_hiber_timeout_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "FALSEI2CTIMEOUT",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_hiber_timeout_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (val < CS40L2X_FALSEI2CTIMEOUT_MIN)
return -EINVAL;
if (val > CS40L2X_FALSEI2CTIMEOUT_MAX)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "FALSEI2CTIMEOUT",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg, val);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio1_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "GPIO_ENABLE",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio1_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "GPIO_ENABLE",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg,
val ? CS40L2X_GPIO1_ENABLED : CS40L2X_GPIO1_DISABLED);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg,
val ? CS40L2X_GPIO1_ENABLED : CS40L2X_GPIO1_DISABLED);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static int cs40l2x_gpio_edge_index_get(struct cs40l2x_private *cs40l2x,
unsigned int *index,
unsigned int gpio_offs, bool gpio_rise)
{
int ret;
bool gpio_pol = cs40l2x->pdata.gpio_indv_pol & (1 << (gpio_offs >> 2));
unsigned int reg = cs40l2x_dsp_reg(cs40l2x,
gpio_pol ^ gpio_rise ? "INDEXBUTTONPRESS" :
"INDEXBUTTONRELEASE",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg)
return -EPERM;
reg += gpio_offs;
if (!(cs40l2x->gpio_mask & (1 << (gpio_offs >> 2))))
return -EPERM;
ret = regmap_read(cs40l2x->regmap, reg, index);
if (cs40l2x->virtual_bin) {
if (*index == cs40l2x->virtual_gpio1_fall_slot) {
*index =
cs40l2x->virtual_gpio_index[CS40L2X_GPIO_FALL];
} else if (*index == cs40l2x->virtual_gpio1_rise_slot) {
*index =
cs40l2x->virtual_gpio_index[CS40L2X_GPIO_RISE];
}
}
return ret;
}
static int cs40l2x_gpio_edge_index_set(struct cs40l2x_private *cs40l2x,
unsigned int index,
unsigned int gpio_offs, bool gpio_rise)
{
bool gpio_pol = cs40l2x->pdata.gpio_indv_pol & (1 << (gpio_offs >> 2));
unsigned int reg = cs40l2x_dsp_reg(cs40l2x,
gpio_pol ^ gpio_rise ? "INDEXBUTTONPRESS" :
"INDEXBUTTONRELEASE",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
int ret;
int r = CS40L2X_GPIO_RISE;
int f = CS40L2X_GPIO_FALL;
if (!reg)
return -EPERM;
reg += gpio_offs;
if (!(cs40l2x->gpio_mask & (1 << (gpio_offs >> 2))))
return -EPERM;
if (!cs40l2x->virtual_bin) {
if (index > (cs40l2x->num_waves - CS40L2X_WT_NUM_VIRT_SLOTS))
return -EINVAL;
} else {
if (index >=
(cs40l2x->num_waves - CS40L2X_WT_NUM_VIRT_SLOTS)) {
if (index >=
((cs40l2x->num_waves +
cs40l2x->num_virtual_waves) -
CS40L2X_WT_NUM_VIRT_SLOTS)) {
return -EINVAL;
}
if (gpio_offs == 0) {
if (gpio_rise) {
index =
cs40l2x->virtual_gpio1_rise_slot;
if (cs40l2x->virtual_gpio_index[r] !=
cs40l2x->loaded_gpio_index[r])
cs40l2x_write_virtual_waveform(
cs40l2x,
cs40l2x->virtual_gpio_index[r],
true, true, false);
/* else virtual wvfrm already loaded */
} else {
index =
cs40l2x->virtual_gpio1_fall_slot;
if (cs40l2x->virtual_gpio_index[f] !=
cs40l2x->loaded_gpio_index[f])
cs40l2x_write_virtual_waveform(
cs40l2x,
cs40l2x->virtual_gpio_index[f],
true, false, false);
}
}
}
}
ret = regmap_write(cs40l2x->regmap, reg, index);
if (ret)
return ret;
return cs40l2x_dsp_cache(cs40l2x, reg, index);
}
static ssize_t cs40l2x_gpio1_rise_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_get(cs40l2x, &index,
CS40L2X_INDEXBUTTONPRESS1, CS40L2X_GPIO_RISE);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", index);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio1_rise_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
cs40l2x->virtual_gpio_index[CS40L2X_GPIO_RISE] = index;
ret = cs40l2x_gpio_edge_index_set(cs40l2x, index,
CS40L2X_INDEXBUTTONPRESS1, CS40L2X_GPIO_RISE);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio1_fall_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_get(cs40l2x, &index,
CS40L2X_INDEXBUTTONRELEASE1, CS40L2X_GPIO_FALL);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", index);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio1_fall_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
cs40l2x->virtual_gpio_index[CS40L2X_GPIO_FALL] = index;
ret = cs40l2x_gpio_edge_index_set(cs40l2x, index,
CS40L2X_INDEXBUTTONRELEASE1, CS40L2X_GPIO_FALL);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio1_fall_timeout_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "PRESS_RELEASE_TIMEOUT",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio1_fall_timeout_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (val > CS40L2X_PR_TIMEOUT_MAX)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "PRESS_RELEASE_TIMEOUT",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg, val);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio2_rise_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_get(cs40l2x, &index,
CS40L2X_INDEXBUTTONPRESS2, CS40L2X_GPIO_RISE);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", index);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio2_rise_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_set(cs40l2x, index,
CS40L2X_INDEXBUTTONPRESS2, CS40L2X_GPIO_RISE);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio2_fall_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_get(cs40l2x, &index,
CS40L2X_INDEXBUTTONRELEASE2, CS40L2X_GPIO_FALL);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", index);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio2_fall_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_set(cs40l2x, index,
CS40L2X_INDEXBUTTONRELEASE2, CS40L2X_GPIO_FALL);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio3_rise_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_get(cs40l2x, &index,
CS40L2X_INDEXBUTTONPRESS3, CS40L2X_GPIO_RISE);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", index);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio3_rise_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_set(cs40l2x, index,
CS40L2X_INDEXBUTTONPRESS3, CS40L2X_GPIO_RISE);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio3_fall_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_get(cs40l2x, &index,
CS40L2X_INDEXBUTTONRELEASE3, CS40L2X_GPIO_FALL);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", index);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio3_fall_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_set(cs40l2x, index,
CS40L2X_INDEXBUTTONRELEASE3, CS40L2X_GPIO_FALL);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio4_rise_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_get(cs40l2x, &index,
CS40L2X_INDEXBUTTONPRESS4, CS40L2X_GPIO_RISE);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", index);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio4_rise_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_set(cs40l2x, index,
CS40L2X_INDEXBUTTONPRESS4, CS40L2X_GPIO_RISE);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio4_fall_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_get(cs40l2x, &index,
CS40L2X_INDEXBUTTONRELEASE4, CS40L2X_GPIO_FALL);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", index);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio4_fall_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio_edge_index_set(cs40l2x, index,
CS40L2X_INDEXBUTTONRELEASE4, CS40L2X_GPIO_FALL);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_standby_timeout_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "EVENT_TIMEOUT",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_standby_timeout_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (val > CS40L2X_EVENT_TIMEOUT_MAX)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "EVENT_TIMEOUT",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg, val);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_f0_measured_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
if (cs40l2x->diag_state < CS40L2X_DIAG_STATE_DONE1) {
ret = -ENODATA;
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%d\n", cs40l2x->f0_measured);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_f0_stored_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "F0_STORED",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG ?
CS40L2X_ALGO_ID_F0 : cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_f0_stored_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (cs40l2x->pdata.f0_min > 0 && val < cs40l2x->pdata.f0_min)
return -EINVAL;
if (cs40l2x->pdata.f0_max > 0 && val > cs40l2x->pdata.f0_max)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "F0_STORED",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG ?
CS40L2X_ALGO_ID_F0 : cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg, val);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_bemf_measured_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
mutex_lock(&cs40l2x->lock);
if (cs40l2x->diag_state < CS40L2X_DIAG_STATE_DONE1) {
ret = -ENODATA;
goto err_bemf;
}
ret = snprintf(buf, PAGE_SIZE, "%d\n", cs40l2x->bemf_measured);
err_bemf:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_bemf_rec_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct regmap *regmap = cs40l2x->regmap;
unsigned int reg, val;
ssize_t len = 0;
int ret, i;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "BEMF_BUFFER",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_PAR);
if (!reg) {
dev_err(dev, "Cannot get the register for the bemf buffer\n");
ret = -EINVAL;
goto err_bemf_rec;
}
for (i = 0; i < CS40L2X_BEMF_BUF_MAX; i++) {
ret = regmap_read(regmap, reg + (i*4), &val);
if (ret)
goto err_bemf_rec;
len += snprintf(buf + len, PAGE_SIZE - len, "%d ", val);
}
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
ret = len;
err_bemf_rec:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_bemf_rec_en_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct regmap *regmap = cs40l2x->regmap;
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret || val > 1)
return -EINVAL;
pm_runtime_get_sync(dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "MEASURE_BEMF_ONLY",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_PAR);
if (!reg) {
dev_err(dev, "Cannot get the register for bemf only\n");
ret = -EINVAL;
goto err_bemf_rec_en;
}
ret = regmap_write(regmap, reg, val);
if (ret)
goto err_bemf_rec_en;
ret = count;
err_bemf_rec_en:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
static ssize_t cs40l2x_bemf_rec_en_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct regmap *regmap = cs40l2x->regmap;
unsigned int reg, val;
int ret;
pm_runtime_get_sync(dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "MEASURE_BEMF_ONLY",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_PAR);
if (!reg) {
dev_err(dev, "Cannot get the register for bemf only\n");
ret = -EINVAL;
goto err_bemf_rec_en;
}
ret = regmap_read(regmap, reg, &val);
if (ret)
goto err_bemf_rec_en;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_bemf_rec_en:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
static ssize_t cs40l2x_bemf_shift_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct regmap *regmap = cs40l2x->regmap;
unsigned int reg, val;
int ret;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "BEMF_SHIFT",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_PAR);
if (!reg) {
dev_err(cs40l2x->dev, "Unable to get bemf shift register\n");
ret = -EPERM;
goto err_exit;
}
ret = regmap_write(regmap, reg, val);
if (ret)
goto err_exit;
ret = count;
err_exit:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_bemf_shift_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct regmap *regmap = cs40l2x->regmap;
unsigned int reg, val;
int ret;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "BEMF_SHIFT",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_PAR);
if (!reg) {
dev_err(cs40l2x->dev, "Unable to get bemf shift register\n");
ret = -EINVAL;
goto err_exit;
}
ret = regmap_read(regmap, reg, &val);
if (ret)
goto err_exit;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_exit:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_dyn_f0_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret = 0, i;
mutex_lock(&cs40l2x->lock);
for (i = 0; i < CS40l2X_F0_MAX_ENTRIES; i++) {
if (!cs40l2x->dynamic_f0[i].changed)
continue;
ret += snprintf(buf, PAGE_SIZE, "%d %d\n",
cs40l2x->dynamic_f0[i].index,
cs40l2x->dynamic_f0[i].f0);
buf += strlen(buf);
}
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_dyn_f0_index_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
mutex_lock(&cs40l2x->lock);
ret = snprintf(buf, PAGE_SIZE, "%d\n", cs40l2x->dynamic_f0_index);
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_dyn_f0_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (val < 0 || val > CS40l2X_F0_MAX_ENTRIES - 1) {
dev_err(dev, "Invalid index value %d\n", val);
return -EINVAL;
}
mutex_lock(&cs40l2x->lock);
cs40l2x->dynamic_f0_index = val;
mutex_unlock(&cs40l2x->lock);
return count;
}
static ssize_t cs40l2x_dyn_f0_val_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct regmap *regmap = cs40l2x->regmap;
unsigned int val, reg;
int ret, i, loc = -1, index;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (val > CS40L2X_DYN_F0_MASK) {
dev_err(dev, "Invalid f0 value %d\n", val);
return -EINVAL;
}
pm_runtime_get_sync(dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "DYN_F0_TABLE", CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_DYN_F0);
if (!reg) {
dev_err(dev, "Cannot get the register for the f0 table\n");
ret = -EINVAL;
goto err_mutex;
}
index = cs40l2x->dynamic_f0_index;
for (i = 0; i < CS40l2X_F0_MAX_ENTRIES; i++) {
if (!cs40l2x->dynamic_f0[i].changed) {
if (loc < 0)
loc = i;
continue;
}
if (index == cs40l2x->dynamic_f0[i].index)
break;
}
/* Nothing exists in the table, start from first available element */
if (i == CS40l2X_F0_MAX_ENTRIES) {
if (loc >= 0) {
i = loc;
} else {
dev_err(dev, "Can't find F0 index.\n");
ret = -EINVAL;
goto err_mutex;
}
}
ret = regmap_write(regmap, reg + (i*4),
val | (index << CS40L2X_DYN_F0_INDEX_SHIFT));
if (ret)
goto err_mutex;
cs40l2x->dynamic_f0[i].f0 = val;
cs40l2x->dynamic_f0[i].index = index;
cs40l2x->dynamic_f0[i].changed = true;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
static ssize_t cs40l2x_dyn_f0_val_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret = 0, i, index;
mutex_lock(&cs40l2x->lock);
index = cs40l2x->dynamic_f0_index;
for (i = 0; i < CS40l2X_F0_MAX_ENTRIES; i++)
if (index == cs40l2x->dynamic_f0[i].index)
break;
if (i == CS40l2X_F0_MAX_ENTRIES) {
dev_err(dev, "Cannot find f0 index %d\n", index);
ret = -EINVAL;
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%d\n", cs40l2x->dynamic_f0[i].f0);
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_f0_offset_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "F0_OFFSET",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_f0_offset_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (val > CS40L2X_F0_OFFSET_POS_MAX && val < CS40L2X_F0_OFFSET_NEG_MIN)
return -EINVAL;
if (val > CS40L2X_F0_OFFSET_NEG_MAX)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "F0_OFFSET",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg, val);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_redc_measured_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
mutex_lock(&cs40l2x->lock);
if (cs40l2x->diag_state < CS40L2X_DIAG_STATE_DONE1) {
ret = -ENODATA;
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%d\n", cs40l2x->redc_measured);
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_redc_stored_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "REDC_STORED",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG ?
CS40L2X_ALGO_ID_F0 : cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_redc_stored_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (cs40l2x->pdata.redc_min > 0 && val < cs40l2x->pdata.redc_min)
return -EINVAL;
if (cs40l2x->pdata.redc_max > 0 && val > cs40l2x->pdata.redc_max)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "REDC_STORED",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG ?
CS40L2X_ALGO_ID_F0 : cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg, val);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_q_measured_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
mutex_lock(&cs40l2x->lock);
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG) {
ret = -EPERM;
goto err_mutex;
}
if (cs40l2x->diag_state < CS40L2X_DIAG_STATE_DONE2) {
ret = -ENODATA;
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%d\n", cs40l2x->q_measured);
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_q_stored_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "Q_STORED",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_q_stored_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (cs40l2x->pdata.q_min > 0 && val < cs40l2x->pdata.q_min)
return -EINVAL;
if (cs40l2x->pdata.q_max > 0 && val > cs40l2x->pdata.q_max)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "Q_STORED",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg, val);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_comp_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
mutex_lock(&cs40l2x->lock);
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL) {
ret = -EPERM;
goto err_mutex;
}
if (cs40l2x->comp_enable_pend) {
ret = -EIO;
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%d\n", cs40l2x->comp_enable);
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_comp_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
cs40l2x->comp_enable_pend = true;
cs40l2x->comp_enable = val > 0;
switch (cs40l2x->fw_desc->id) {
case CS40L2X_FW_ID_CAL:
ret = -EPERM;
break;
case CS40L2X_FW_ID_ORIG:
ret = regmap_write(cs40l2x->regmap,
cs40l2x_dsp_reg(cs40l2x, "COMPENSATION_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE),
cs40l2x->comp_enable);
break;
default:
ret = regmap_write(cs40l2x->regmap,
cs40l2x_dsp_reg(cs40l2x, "COMPENSATION_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE),
(cs40l2x->comp_enable
& cs40l2x->comp_enable_redc)
<< CS40L2X_COMP_EN_REDC_SHIFT |
(cs40l2x->comp_enable
& cs40l2x->comp_enable_f0)
<< CS40L2X_COMP_EN_F0_SHIFT);
}
if (ret)
goto err_mutex;
cs40l2x->comp_enable_pend = false;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_redc_comp_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
mutex_lock(&cs40l2x->lock);
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL
|| cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG) {
ret = -EPERM;
goto err_mutex;
}
if (cs40l2x->comp_enable_pend) {
ret = -EIO;
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%d\n", cs40l2x->comp_enable_redc);
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_redc_comp_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
cs40l2x->comp_enable_pend = true;
cs40l2x->comp_enable_redc = val > 0;
switch (cs40l2x->fw_desc->id) {
case CS40L2X_FW_ID_CAL:
case CS40L2X_FW_ID_ORIG:
ret = -EPERM;
break;
default:
ret = regmap_write(cs40l2x->regmap,
cs40l2x_dsp_reg(cs40l2x, "COMPENSATION_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE),
(cs40l2x->comp_enable
& cs40l2x->comp_enable_redc)
<< CS40L2X_COMP_EN_REDC_SHIFT |
(cs40l2x->comp_enable
& cs40l2x->comp_enable_f0)
<< CS40L2X_COMP_EN_F0_SHIFT);
}
if (ret)
goto err_mutex;
cs40l2x->comp_enable_pend = false;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static int cs40l2x_dig_scale_get(struct cs40l2x_private *cs40l2x,
unsigned int *dig_scale)
{
int ret;
unsigned int val;
ret = regmap_read(cs40l2x->regmap, CS40L2X_AMP_DIG_VOL_CTRL, &val);
if (ret)
return ret;
*dig_scale = (CS40L2X_DIG_SCALE_ZERO - ((val & CS40L2X_AMP_VOL_PCM_MASK)
>> CS40L2X_AMP_VOL_PCM_SHIFT)) & CS40L2X_DIG_SCALE_MASK;
return 0;
}
static int cs40l2x_dig_scale_set(struct cs40l2x_private *cs40l2x,
unsigned int dig_scale)
{
int ret;
unsigned int val;
if (dig_scale == CS40L2X_DIG_SCALE_RESET)
return -EINVAL;
ret = regmap_read(cs40l2x->regmap, CS40L2X_AMP_DIG_VOL_CTRL, &val);
if (ret)
return ret;
val &= ~CS40L2X_AMP_VOL_PCM_MASK;
val |= CS40L2X_AMP_VOL_PCM_MASK &
(((CS40L2X_DIG_SCALE_ZERO - dig_scale)
& CS40L2X_DIG_SCALE_MASK) << CS40L2X_AMP_VOL_PCM_SHIFT);
ret = regmap_write(cs40l2x->regmap, CS40L2X_AMP_DIG_VOL_CTRL, val);
if (ret)
return ret;
return cs40l2x_wseq_replace(cs40l2x, CS40L2X_AMP_DIG_VOL_CTRL, val);
}
static ssize_t cs40l2x_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
/*
* this operation is agnostic to the variable firmware ID and may
* therefore be performed without mutex protection
*/
ret = cs40l2x_dig_scale_get(cs40l2x, &dig_scale);
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
if (ret)
return ret;
return snprintf(buf, PAGE_SIZE, "%d\n", dig_scale);
}
static ssize_t cs40l2x_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
if (dig_scale > CS40L2X_DIG_SCALE_MAX)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
/*
* this operation calls cs40l2x_wseq_replace which checks the variable
* firmware ID and must therefore be performed within mutex protection
*/
ret = cs40l2x_dig_scale_set(cs40l2x, dig_scale);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static int cs40l2x_gpio1_dig_scale_get(struct cs40l2x_private *cs40l2x,
unsigned int *dig_scale)
{
unsigned int val, reg;
int ret;
reg = cs40l2x_dsp_reg(cs40l2x, "GAIN_CONTROL",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg)
return -EPERM;
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
return ret;
*dig_scale = (val & CS40L2X_GAIN_CTRL_GPIO_MASK)
>> CS40L2X_GAIN_CTRL_GPIO_SHIFT;
return 0;
}
static int cs40l2x_gpio1_dig_scale_set(struct cs40l2x_private *cs40l2x,
unsigned int dig_scale)
{
unsigned int val, reg;
int ret;
reg = cs40l2x_dsp_reg(cs40l2x, "GAIN_CONTROL",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg)
return -EPERM;
if (dig_scale == CS40L2X_DIG_SCALE_RESET)
return -EINVAL;
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
return ret;
val &= ~CS40L2X_GAIN_CTRL_GPIO_MASK;
val |= CS40L2X_GAIN_CTRL_GPIO_MASK &
(dig_scale << CS40L2X_GAIN_CTRL_GPIO_SHIFT);
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
return ret;
return cs40l2x_dsp_cache(cs40l2x, reg, val);
}
static ssize_t cs40l2x_gpio1_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale = 0;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio1_dig_scale_get(cs40l2x, &dig_scale);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", dig_scale);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio1_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
if (dig_scale > CS40L2X_DIG_SCALE_MAX)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_gpio1_dig_scale_set(cs40l2x, dig_scale);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static int cs40l2x_gpio_edge_dig_scale_get(struct cs40l2x_private *cs40l2x,
unsigned int *dig_scale,
unsigned int gpio_offs, bool gpio_rise)
{
bool gpio_pol = cs40l2x->pdata.gpio_indv_pol & (1 << (gpio_offs >> 2));
unsigned int val, reg;
int ret;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "GPIO_GAIN",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
reg += gpio_offs;
if (!(cs40l2x->gpio_mask & (1 << (gpio_offs >> 2)))) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
*dig_scale = (val & (gpio_pol ^ gpio_rise ?
CS40L2X_GPIO_GAIN_RISE_MASK :
CS40L2X_GPIO_GAIN_FALL_MASK)) >>
(gpio_pol ^ gpio_rise ?
CS40L2X_GPIO_GAIN_RISE_SHIFT :
CS40L2X_GPIO_GAIN_FALL_SHIFT);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static int cs40l2x_gpio_edge_dig_scale_set(struct cs40l2x_private *cs40l2x,
unsigned int dig_scale,
unsigned int gpio_offs, bool gpio_rise)
{
bool gpio_pol = cs40l2x->pdata.gpio_indv_pol & (1 << (gpio_offs >> 2));
unsigned int val, reg;
int ret;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "GPIO_GAIN",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
reg += gpio_offs;
if (!(cs40l2x->gpio_mask & (1 << (gpio_offs >> 2)))) {
ret = -EPERM;
goto err_mutex;
}
if (dig_scale == CS40L2X_DIG_SCALE_RESET
|| dig_scale > CS40L2X_DIG_SCALE_MAX) {
ret = -EINVAL;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
val &= ~(gpio_pol ^ gpio_rise ?
CS40L2X_GPIO_GAIN_RISE_MASK :
CS40L2X_GPIO_GAIN_FALL_MASK);
val |= (gpio_pol ^ gpio_rise ?
CS40L2X_GPIO_GAIN_RISE_MASK :
CS40L2X_GPIO_GAIN_FALL_MASK) &
(dig_scale << (gpio_pol ^ gpio_rise ?
CS40L2X_GPIO_GAIN_RISE_SHIFT :
CS40L2X_GPIO_GAIN_FALL_SHIFT));
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg, val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_gpio1_rise_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = cs40l2x_gpio_edge_dig_scale_get(cs40l2x, &dig_scale,
CS40L2X_INDEXBUTTONPRESS1, CS40L2X_GPIO_RISE);
if (ret)
return ret;
return snprintf(buf, PAGE_SIZE, "%u\n", dig_scale);
}
static ssize_t cs40l2x_gpio1_rise_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
ret = cs40l2x_gpio_edge_dig_scale_set(cs40l2x, dig_scale,
CS40L2X_INDEXBUTTONPRESS1, CS40L2X_GPIO_RISE);
if (ret)
return ret;
return count;
}
static ssize_t cs40l2x_gpio1_fall_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = cs40l2x_gpio_edge_dig_scale_get(cs40l2x, &dig_scale,
CS40L2X_INDEXBUTTONRELEASE1, CS40L2X_GPIO_FALL);
if (ret)
return ret;
return snprintf(buf, PAGE_SIZE, "%u\n", dig_scale);
}
static ssize_t cs40l2x_gpio1_fall_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
ret = cs40l2x_gpio_edge_dig_scale_set(cs40l2x, dig_scale,
CS40L2X_INDEXBUTTONRELEASE1, CS40L2X_GPIO_FALL);
if (ret)
return ret;
return count;
}
static ssize_t cs40l2x_gpio2_rise_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = cs40l2x_gpio_edge_dig_scale_get(cs40l2x, &dig_scale,
CS40L2X_INDEXBUTTONPRESS2, CS40L2X_GPIO_RISE);
if (ret)
return ret;
return snprintf(buf, PAGE_SIZE, "%u\n", dig_scale);
}
static ssize_t cs40l2x_gpio2_rise_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
ret = cs40l2x_gpio_edge_dig_scale_set(cs40l2x, dig_scale,
CS40L2X_INDEXBUTTONPRESS2, CS40L2X_GPIO_RISE);
if (ret)
return ret;
return count;
}
static ssize_t cs40l2x_gpio2_fall_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = cs40l2x_gpio_edge_dig_scale_get(cs40l2x, &dig_scale,
CS40L2X_INDEXBUTTONRELEASE2, CS40L2X_GPIO_FALL);
if (ret)
return ret;
return snprintf(buf, PAGE_SIZE, "%u\n", dig_scale);
}
static ssize_t cs40l2x_gpio2_fall_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
ret = cs40l2x_gpio_edge_dig_scale_set(cs40l2x, dig_scale,
CS40L2X_INDEXBUTTONRELEASE2, CS40L2X_GPIO_FALL);
if (ret)
return ret;
return count;
}
static ssize_t cs40l2x_gpio3_rise_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = cs40l2x_gpio_edge_dig_scale_get(cs40l2x, &dig_scale,
CS40L2X_INDEXBUTTONPRESS3, CS40L2X_GPIO_RISE);
if (ret)
return ret;
return snprintf(buf, PAGE_SIZE, "%u\n", dig_scale);
}
static ssize_t cs40l2x_gpio3_rise_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
ret = cs40l2x_gpio_edge_dig_scale_set(cs40l2x, dig_scale,
CS40L2X_INDEXBUTTONPRESS3, CS40L2X_GPIO_RISE);
if (ret)
return ret;
return count;
}
static ssize_t cs40l2x_gpio3_fall_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = cs40l2x_gpio_edge_dig_scale_get(cs40l2x, &dig_scale,
CS40L2X_INDEXBUTTONRELEASE3, CS40L2X_GPIO_FALL);
if (ret)
return ret;
return snprintf(buf, PAGE_SIZE, "%u\n", dig_scale);
}
static ssize_t cs40l2x_gpio3_fall_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
ret = cs40l2x_gpio_edge_dig_scale_set(cs40l2x, dig_scale,
CS40L2X_INDEXBUTTONRELEASE3, CS40L2X_GPIO_FALL);
if (ret)
return ret;
return count;
}
static ssize_t cs40l2x_gpio4_rise_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = cs40l2x_gpio_edge_dig_scale_get(cs40l2x, &dig_scale,
CS40L2X_INDEXBUTTONPRESS4, CS40L2X_GPIO_RISE);
if (ret)
return ret;
return snprintf(buf, PAGE_SIZE, "%u\n", dig_scale);
}
static ssize_t cs40l2x_gpio4_rise_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
ret = cs40l2x_gpio_edge_dig_scale_set(cs40l2x, dig_scale,
CS40L2X_INDEXBUTTONPRESS4, CS40L2X_GPIO_RISE);
if (ret)
return ret;
return count;
}
static ssize_t cs40l2x_gpio4_fall_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = cs40l2x_gpio_edge_dig_scale_get(cs40l2x, &dig_scale,
CS40L2X_INDEXBUTTONRELEASE4, CS40L2X_GPIO_FALL);
if (ret)
return ret;
return snprintf(buf, PAGE_SIZE, "%u\n", dig_scale);
}
static ssize_t cs40l2x_gpio4_fall_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
ret = cs40l2x_gpio_edge_dig_scale_set(cs40l2x, dig_scale,
CS40L2X_INDEXBUTTONRELEASE4, CS40L2X_GPIO_FALL);
if (ret)
return ret;
return count;
}
static int cs40l2x_cp_dig_scale_get(struct cs40l2x_private *cs40l2x,
unsigned int *dig_scale)
{
unsigned int val;
unsigned int reg = cs40l2x_dsp_reg(cs40l2x, "GAIN_CONTROL",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
int ret;
if (!reg)
return -EPERM;
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
return ret;
*dig_scale = (val & CS40L2X_GAIN_CTRL_TRIG_MASK)
>> CS40L2X_GAIN_CTRL_TRIG_SHIFT;
return 0;
}
static int cs40l2x_cp_dig_scale_set(struct cs40l2x_private *cs40l2x,
unsigned int dig_scale)
{
unsigned int val;
unsigned int reg = cs40l2x_dsp_reg(cs40l2x, "GAIN_CONTROL",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
int ret;
if (!reg)
return -EPERM;
if (dig_scale == CS40L2X_DIG_SCALE_RESET)
return -EINVAL;
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
return ret;
val &= ~CS40L2X_GAIN_CTRL_TRIG_MASK;
val |= CS40L2X_GAIN_CTRL_TRIG_MASK &
(dig_scale << CS40L2X_GAIN_CTRL_TRIG_SHIFT);
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
return ret;
return cs40l2x_dsp_cache(cs40l2x, reg, val);
}
static ssize_t cs40l2x_cp_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_cp_dig_scale_get(cs40l2x, &dig_scale);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", dig_scale);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_cp_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
if (dig_scale > CS40L2X_DIG_SCALE_MAX)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_cp_dig_scale_set(cs40l2x, dig_scale);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_heartbeat_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = regmap_read(cs40l2x->regmap,
cs40l2x_dsp_reg(cs40l2x, "HALO_HEARTBEAT",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
&val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_num_waves_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int num_waves;
mutex_lock(&cs40l2x->lock);
num_waves = cs40l2x->num_waves;
if (cs40l2x->virtual_bin)
num_waves = (cs40l2x->num_waves - CS40L2X_WT_NUM_VIRT_SLOTS) +
cs40l2x->num_virtual_waves;
/* The minus is for the virtual slots */
mutex_unlock(&cs40l2x->lock);
return snprintf(buf, PAGE_SIZE, "%d\n", num_waves);
}
static ssize_t cs40l2x_num_virtual_waves_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int num_virtual_waves;
mutex_lock(&cs40l2x->lock);
num_virtual_waves = cs40l2x->num_virtual_waves;
mutex_unlock(&cs40l2x->lock);
return snprintf(buf, PAGE_SIZE, "%d\n", num_virtual_waves);
}
static ssize_t cs40l2x_num_virtual_composite_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int num_virtual_comp_waves;
mutex_lock(&cs40l2x->lock);
num_virtual_comp_waves = (cs40l2x->num_virtual_waves -
cs40l2x->num_virtual_pwle_waves);
mutex_unlock(&cs40l2x->lock);
return snprintf(buf, PAGE_SIZE, "%d\n", num_virtual_comp_waves);
}
static ssize_t cs40l2x_num_virtual_pwle_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int num_virtual_pwle_waves;
mutex_lock(&cs40l2x->lock);
num_virtual_pwle_waves = cs40l2x->num_virtual_pwle_waves;
mutex_unlock(&cs40l2x->lock);
return snprintf(buf, PAGE_SIZE, "%d\n", num_virtual_pwle_waves);
}
static ssize_t cs40l2x_fw_rev_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int fw_rev;
mutex_lock(&cs40l2x->lock);
fw_rev = cs40l2x->algo_info[0].rev;
mutex_unlock(&cs40l2x->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", fw_rev);
}
static ssize_t cs40l2x_fw_id_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int fw_id;
mutex_lock(&cs40l2x->lock);
fw_id = cs40l2x->fw_desc->id;
mutex_unlock(&cs40l2x->lock);
return snprintf(buf, PAGE_SIZE, "0x%06X\n", fw_id);
}
static ssize_t cs40l2x_fw_swap_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int fw_id;
int ret;
ret = kstrtou32(buf, 16, &fw_id);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
if (fw_id == cs40l2x->fw_desc->id)
goto err_exit;
if (fw_id == CS40L2X_FW_ID_ORIG || fw_id == CS40L2X_FW_ID_B1ROM) {
ret = -EINVAL;
goto err_exit;
}
if (cs40l2x->revid < CS40L2X_REVID_B1) {
ret = -EPERM;
goto err_exit;
}
ret = cs40l2x_firmware_swap(cs40l2x, fw_id);
if (ret) {
dev_err(cs40l2x->dev, "Failed to swap firmware: %d\n", ret);
goto err_exit;
}
cs40l2x->fw_id_remap = fw_id;
cs40l2x->dsp_cache_depth = 0;
if (cs40l2x->pbq_state != CS40L2X_PBQ_STATE_IDLE) {
ret = cs40l2x_cp_dig_scale_set(cs40l2x, cs40l2x->pbq_cp_dig_scale);
if (ret)
goto err_exit;
cs40l2x->pbq_state = CS40L2X_PBQ_STATE_IDLE;
}
if (cs40l2x->cp_trigger_index == cs40l2x->virtual_slot_index)
cs40l2x_write_virtual_waveform(cs40l2x,
cs40l2x->loaded_virtual_index,
false, false, false);
dev_info(cs40l2x->dev, "Successfully swapped firmware to 0x%06X\n",
fw_id);
err_exit:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
if (ret)
return ret;
return count;
}
static ssize_t cs40l2x_vpp_measured_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
if (cs40l2x->vpp_measured < 0)
return -ENODATA;
return snprintf(buf, PAGE_SIZE, "%d\n", cs40l2x->vpp_measured);
}
static ssize_t cs40l2x_ipp_measured_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
if (cs40l2x->ipp_measured < 0)
return -ENODATA;
return snprintf(buf, PAGE_SIZE, "%d\n", cs40l2x->ipp_measured);
}
static ssize_t cs40l2x_vbatt_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "VPMONMAX",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
if (val == CS40L2X_VPMONMAX_RESET) {
ret = -ENODATA;
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%u\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_vbatt_max_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (val)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "VPMONMAX",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, CS40L2X_VPMONMAX_RESET);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_vbatt_min_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "VPMONMIN",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
if (val == CS40L2X_VPMONMIN_RESET) {
ret = -ENODATA;
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%u\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_vbatt_min_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (val)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "VPMONMIN",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, CS40L2X_VPMONMIN_RESET);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_exc_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "EX_PROTECT_ENABLED",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_EXC);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_exc_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "EX_PROTECT_ENABLED",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_EXC);
if (!reg || !cs40l2x->exc_available) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap,
reg, val ? CS40L2X_EXC_ENABLED : CS40L2X_EXC_DISABLED);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x,
reg, val ? CS40L2X_EXC_ENABLED : CS40L2X_EXC_DISABLED);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_hw_err_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
ssize_t len = 0;
int ret, i;
mutex_lock(&cs40l2x->lock);
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL
|| cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG) {
ret = -EPERM;
goto err_mutex;
}
for (i = 0; i < CS40L2X_NUM_HW_ERRS; i++)
len += snprintf(buf + len, PAGE_SIZE - len, "%u %s error(s)\n",
cs40l2x->hw_err_count[i],
cs40l2x_hw_errs[i].err_name);
ret = len;
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_hw_err_count_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret, i;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (val)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL
|| cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG) {
ret = -EPERM;
goto err_mutex;
}
for (i = 0; i < CS40L2X_NUM_HW_ERRS; i++) {
if (cs40l2x->hw_err_count[i] > CS40L2X_HW_ERR_COUNT_MAX) {
ret = cs40l2x_hw_err_rls(cs40l2x,
cs40l2x_hw_errs[i].irq_mask);
if (ret)
goto err_mutex;
}
cs40l2x->hw_err_count[i] = 0;
}
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_hw_reset_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
return snprintf(buf, PAGE_SIZE, "%d\n",
gpiod_get_value_cansleep(cs40l2x->reset_gpio));
}
static ssize_t cs40l2x_hw_reset_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct i2c_client *i2c_client = to_i2c_client(cs40l2x->dev);
int ret, state;
unsigned int val, fw_id_restore;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (cs40l2x->revid < CS40L2X_REVID_B1)
return -EPERM;
state = gpiod_get_value_cansleep(cs40l2x->reset_gpio);
if (state < 0)
return state;
/*
* resetting the device prompts it to briefly assert the /ALERT pin,
* so disable the interrupt line until the device has been restored
*/
disable_irq(i2c_client->irq);
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
if (cs40l2x->vibe_state == CS40L2X_VIBE_STATE_RUNNING) {
ret = -EPERM;
goto err_mutex;
}
if (val && !state) {
gpiod_set_value_cansleep(cs40l2x->reset_gpio, 1);
usleep_range(1000, 1100);
fw_id_restore = cs40l2x->fw_desc->id;
cs40l2x->fw_desc = cs40l2x_firmware_match(cs40l2x,
CS40L2X_FW_ID_B1ROM);
ret = cs40l2x_firmware_swap(cs40l2x, fw_id_restore);
if (ret)
goto err_mutex;
cs40l2x->dsp_cache_depth = 0;
} else if (!val && state) {
gpiod_set_value_cansleep(cs40l2x->reset_gpio, 0);
usleep_range(2000, 2100);
}
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
enable_irq(i2c_client->irq);
return ret;
}
static ssize_t cs40l2x_wt_file_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
mutex_lock(&cs40l2x->lock);
if (!strncmp(cs40l2x->wt_file,
CS40L2X_WT_FILE_NAME_MISSING,
CS40L2X_WT_FILE_NAME_LEN_MAX)) {
ret = -ENODATA;
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%s\n", cs40l2x->wt_file);
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_wt_file_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
char wt_file[CS40L2X_WT_FILE_NAME_LEN_MAX];
size_t len = count;
int ret;
if (!len)
return -EINVAL;
if (buf[len - 1] == '\n')
len--;
if (len + 1 > CS40L2X_WT_FILE_NAME_LEN_MAX)
return -ENAMETOOLONG;
memcpy(wt_file, buf, len);
wt_file[len] = '\0';
if (!strncmp(wt_file,
CS40L2X_WT_FILE_NAME_MISSING,
CS40L2X_WT_FILE_NAME_LEN_MAX))
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL
|| cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG) {
ret = -EPERM;
goto err_mutex;
}
ret = cs40l2x_wavetable_swap(cs40l2x, wt_file);
if (ret)
goto err_mutex;
ret = cs40l2x_wavetable_sync(cs40l2x);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_wt_date_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
mutex_lock(&cs40l2x->lock);
if (!strncmp(cs40l2x->wt_date,
CS40L2X_WT_FILE_DATE_MISSING,
CS40L2X_WT_FILE_DATE_LEN_MAX)) {
ret = -ENODATA;
goto err_mutex;
}
ret = snprintf(buf, PAGE_SIZE, "%s\n", cs40l2x->wt_date);
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static int cs40l2x_imon_offs_sync(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
unsigned int reg_calc_enable = cs40l2x_dsp_reg(cs40l2x,
"VMON_IMON_OFFSET_ENABLE",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
unsigned int val_calc_enable = CS40L2X_IMON_OFFS_CALC_DIS;
unsigned int reg, val;
int ret;
if (!reg_calc_enable)
return 0;
reg = cs40l2x_dsp_reg(cs40l2x, "CLAB_ENABLED",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_CLAB);
if (reg) {
ret = regmap_read(regmap, reg, &val);
if (ret)
return ret;
if (val == CS40L2X_CLAB_ENABLED)
val_calc_enable = CS40L2X_IMON_OFFS_CALC_EN;
}
return regmap_write(regmap, reg_calc_enable, val_calc_enable);
}
static ssize_t cs40l2x_imon_offs_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "VMON_IMON_OFFSET_ENABLE",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_imon_offs_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "VMON_IMON_OFFSET_ENABLE",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg,
val ? CS40L2X_IMON_OFFS_CALC_EN :
CS40L2X_IMON_OFFS_CALC_DIS);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg,
val ? CS40L2X_IMON_OFFS_CALC_EN :
CS40L2X_IMON_OFFS_CALC_DIS);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_clab_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "CLAB_ENABLED",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_CLAB);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_clab_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "CLAB_ENABLED",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_CLAB);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg,
val ? CS40L2X_CLAB_ENABLED : CS40L2X_CLAB_DISABLED);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg,
val ? CS40L2X_CLAB_ENABLED : CS40L2X_CLAB_DISABLED);
if (ret)
goto err_mutex;
ret = cs40l2x_imon_offs_sync(cs40l2x);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_clab_peak_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "PEAK_AMPLITUDE_CONTROL",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_CLAB);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_clab_peak_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
if (val > CS40L2X_CLAB_PEAK_MAX)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "PEAK_AMPLITUDE_CONTROL",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_CLAB);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg, val);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_par_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "PWLE_REGULATION_ENABLED",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_PAR);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_par_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "PWLE_REGULATION_ENABLED",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_PAR);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg,
val ? CS40L2X_PAR_ENABLED : CS40L2X_PAR_DISABLED);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg,
val ? CS40L2X_PAR_ENABLED : CS40L2X_PAR_DISABLED);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_par_gain_comp_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "PCM_GAIN_COMPENSATION_ENABLED",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_PAR);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_par_gain_comp_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "PCM_GAIN_COMPENSATION_ENABLED",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_PAR);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg,
val ? CS40L2X_GC_ENABLED : CS40L2X_GC_DISABLED);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_cache(cs40l2x, reg,
val ? CS40L2X_GC_ENABLED : CS40L2X_GC_DISABLED);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_vibe_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
flush_workqueue(cs40l2x->vibe_workqueue);
mutex_lock(&cs40l2x->lock);
ret = snprintf(buf, PAGE_SIZE, "%u\n", cs40l2x->vibe_state);
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_gpio_event_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
mutex_lock(&cs40l2x->lock);
ret = snprintf(buf, PAGE_SIZE, "%u\n", cs40l2x->gpio_event);
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_gpio_event_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret || val != 0)
return -EINVAL;
mutex_lock(&cs40l2x->lock);
cs40l2x_set_gpio_event(cs40l2x, false);
mutex_unlock(&cs40l2x->lock);
return count;
}
static ssize_t cs40l2x_safe_save_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
mutex_lock(&cs40l2x->lock);
ret = snprintf(buf, PAGE_SIZE, "%u\n", cs40l2x->safe_save_state);
mutex_unlock(&cs40l2x->lock);
return ret;
}
static ssize_t cs40l2x_max_back_emf_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "MAXBACKEMF",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_F0);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
ret = snprintf(buf, PAGE_SIZE, "%u\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_max_back_emf_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "MAXBACKEMF",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_F0);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_autosuspend_delay_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
unsigned int val;
mutex_lock(&cs40l2x->lock);
val = cs40l2x->autosuspend_delay;
mutex_unlock(&cs40l2x->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", val);
}
static ssize_t cs40l2x_autosuspend_delay_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
mutex_lock(&cs40l2x->lock);
cs40l2x->autosuspend_delay = val;
mutex_unlock(&cs40l2x->lock);
pm_runtime_set_autosuspend_delay(cs40l2x->dev, val);
return count;
}
static ssize_t cs40l2x_boost_ipk_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
return sysfs_emit(buf, "%d\n", cs40l2x->pdata.boost_ipk);
}
static ssize_t cs40l2x_boost_ipk_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
struct regmap *regmap = cs40l2x->regmap;
int ret;
unsigned int boost_ipk;
unsigned int bst_ipk_scaled;
ret = kstrtou32(buf, 10, &boost_ipk);
if (ret)
return -EINVAL;
if (boost_ipk == cs40l2x->pdata.boost_ipk)
return count;
if ((boost_ipk < 1600) || (boost_ipk > 4500)) {
dev_err(dev, "Invalid boost inductor peak current: %d mA\n", boost_ipk);
return -EINVAL;
}
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
bst_ipk_scaled = ((boost_ipk - 1600) / 50) + 0x10;
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_PEAK_CUR,
CS40L2X_BST_IPK_MASK, bst_ipk_scaled << CS40L2X_BST_IPK_SHIFT);
if (ret) {
dev_err(dev, "Failed to write boost inductor peak current:%d\n", ret);
goto err_mutex;
}
bst_ipk_scaled &= CS40L2X_BST_IPK_MASK;
ret = cs40l2x_wseq_replace(cs40l2x, CS40L2X_AMP_DIG_VOL_CTRL, bst_ipk_scaled);
if (ret) {
dev_err(dev, "Failed to replace boost inductor peak current:%d\n", ret);
goto err_mutex;
}
cs40l2x->pdata.boost_ipk = boost_ipk;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return count;
}
static ssize_t cs40l2x_pwle_ramp_down_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "RAMPDOWN_COEFF",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
ret = regmap_read(cs40l2x->regmap, reg, &val);
if (ret)
goto err_mutex;
/* Q0.24 format */
ret = snprintf(buf, PAGE_SIZE, "%u\n", val);
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static ssize_t cs40l2x_pwle_ramp_down_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct cs40l2x_private *cs40l2x = cs40l2x_get_private(dev);
int ret;
unsigned int reg, val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
reg = cs40l2x_dsp_reg(cs40l2x, "RAMPDOWN_COEFF",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id);
if (!reg) {
ret = -EPERM;
goto err_mutex;
}
/* Q0.24 format */
/* Zero value means that PWLE Ramp down is off */
ret = regmap_write(cs40l2x->regmap, reg, val);
if (ret)
goto err_mutex;
ret = count;
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret;
}
static DEVICE_ATTR(cp_trigger_index, 0660, cs40l2x_cp_trigger_index_show,
cs40l2x_cp_trigger_index_store);
static DEVICE_ATTR(cp_trigger_queue, 0660, cs40l2x_cp_trigger_queue_show,
cs40l2x_cp_trigger_queue_store);
static DEVICE_ATTR(pwle_ramp_down, 0660, cs40l2x_pwle_ramp_down_show,
cs40l2x_pwle_ramp_down_store);
static DEVICE_ATTR(cp_trigger_duration, 0660, cs40l2x_cp_trigger_duration_show,
NULL);
static DEVICE_ATTR(cp_trigger_q_sub, 0660, cs40l2x_cp_trigger_q_sub_show,
NULL);
static DEVICE_ATTR(hiber_cmd, 0660, NULL, cs40l2x_hiber_cmd_store);
static DEVICE_ATTR(hiber_timeout, 0660, cs40l2x_hiber_timeout_show,
cs40l2x_hiber_timeout_store);
static DEVICE_ATTR(gpio1_enable, 0660, cs40l2x_gpio1_enable_show,
cs40l2x_gpio1_enable_store);
static DEVICE_ATTR(gpio1_rise_index, 0660, cs40l2x_gpio1_rise_index_show,
cs40l2x_gpio1_rise_index_store);
static DEVICE_ATTR(gpio1_fall_index, 0660, cs40l2x_gpio1_fall_index_show,
cs40l2x_gpio1_fall_index_store);
static DEVICE_ATTR(gpio1_fall_timeout, 0660, cs40l2x_gpio1_fall_timeout_show,
cs40l2x_gpio1_fall_timeout_store);
static DEVICE_ATTR(gpio2_rise_index, 0660, cs40l2x_gpio2_rise_index_show,
cs40l2x_gpio2_rise_index_store);
static DEVICE_ATTR(gpio2_fall_index, 0660, cs40l2x_gpio2_fall_index_show,
cs40l2x_gpio2_fall_index_store);
static DEVICE_ATTR(gpio3_rise_index, 0660, cs40l2x_gpio3_rise_index_show,
cs40l2x_gpio3_rise_index_store);
static DEVICE_ATTR(gpio3_fall_index, 0660, cs40l2x_gpio3_fall_index_show,
cs40l2x_gpio3_fall_index_store);
static DEVICE_ATTR(gpio4_rise_index, 0660, cs40l2x_gpio4_rise_index_show,
cs40l2x_gpio4_rise_index_store);
static DEVICE_ATTR(gpio4_fall_index, 0660, cs40l2x_gpio4_fall_index_show,
cs40l2x_gpio4_fall_index_store);
static DEVICE_ATTR(standby_timeout, 0660, cs40l2x_standby_timeout_show,
cs40l2x_standby_timeout_store);
static DEVICE_ATTR(f0_measured, 0660, cs40l2x_f0_measured_show, NULL);
static DEVICE_ATTR(f0_stored, 0660, cs40l2x_f0_stored_show,
cs40l2x_f0_stored_store);
static DEVICE_ATTR(bemf_measured, 0660, cs40l2x_bemf_measured_show, NULL);
static DEVICE_ATTR(bemf_rec, 0660, cs40l2x_bemf_rec_show, NULL);
static DEVICE_ATTR(bemf_rec_en, 0660, cs40l2x_bemf_rec_en_show,
cs40l2x_bemf_rec_en_store);
static DEVICE_ATTR(bemf_shift, 0660, cs40l2x_bemf_shift_show,
cs40l2x_bemf_shift_store);
static DEVICE_ATTR(dynamic_f0, 0660, cs40l2x_dyn_f0_show, NULL);
static DEVICE_ATTR(dynamic_f0_index, 0660, cs40l2x_dyn_f0_index_show,
cs40l2x_dyn_f0_index_store);
static DEVICE_ATTR(dynamic_f0_val, 0660, cs40l2x_dyn_f0_val_show,
cs40l2x_dyn_f0_val_store);
static DEVICE_ATTR(f0_offset, 0660, cs40l2x_f0_offset_show,
cs40l2x_f0_offset_store);
static DEVICE_ATTR(redc_measured, 0660, cs40l2x_redc_measured_show, NULL);
static DEVICE_ATTR(redc_stored, 0660, cs40l2x_redc_stored_show,
cs40l2x_redc_stored_store);
static DEVICE_ATTR(q_measured, 0660, cs40l2x_q_measured_show, NULL);
static DEVICE_ATTR(q_stored, 0660, cs40l2x_q_stored_show,
cs40l2x_q_stored_store);
static DEVICE_ATTR(comp_enable, 0660, cs40l2x_comp_enable_show,
cs40l2x_comp_enable_store);
static DEVICE_ATTR(redc_comp_enable, 0660, cs40l2x_redc_comp_enable_show,
cs40l2x_redc_comp_enable_store);
static DEVICE_ATTR(dig_scale, 0660, cs40l2x_dig_scale_show,
cs40l2x_dig_scale_store);
static DEVICE_ATTR(gpio1_dig_scale, 0660, cs40l2x_gpio1_dig_scale_show,
cs40l2x_gpio1_dig_scale_store);
static DEVICE_ATTR(gpio1_rise_dig_scale, 0660,
cs40l2x_gpio1_rise_dig_scale_show,
cs40l2x_gpio1_rise_dig_scale_store);
static DEVICE_ATTR(gpio1_fall_dig_scale, 0660,
cs40l2x_gpio1_fall_dig_scale_show,
cs40l2x_gpio1_fall_dig_scale_store);
static DEVICE_ATTR(gpio2_rise_dig_scale, 0660,
cs40l2x_gpio2_rise_dig_scale_show,
cs40l2x_gpio2_rise_dig_scale_store);
static DEVICE_ATTR(gpio2_fall_dig_scale, 0660,
cs40l2x_gpio2_fall_dig_scale_show,
cs40l2x_gpio2_fall_dig_scale_store);
static DEVICE_ATTR(gpio3_rise_dig_scale, 0660,
cs40l2x_gpio3_rise_dig_scale_show,
cs40l2x_gpio3_rise_dig_scale_store);
static DEVICE_ATTR(gpio3_fall_dig_scale, 0660,
cs40l2x_gpio3_fall_dig_scale_show,
cs40l2x_gpio3_fall_dig_scale_store);
static DEVICE_ATTR(gpio4_rise_dig_scale, 0660,
cs40l2x_gpio4_rise_dig_scale_show,
cs40l2x_gpio4_rise_dig_scale_store);
static DEVICE_ATTR(gpio4_fall_dig_scale, 0660,
cs40l2x_gpio4_fall_dig_scale_show,
cs40l2x_gpio4_fall_dig_scale_store);
static DEVICE_ATTR(cp_dig_scale, 0660, cs40l2x_cp_dig_scale_show,
cs40l2x_cp_dig_scale_store);
static DEVICE_ATTR(heartbeat, 0660, cs40l2x_heartbeat_show, NULL);
static DEVICE_ATTR(num_waves, 0660, cs40l2x_num_waves_show, NULL);
static DEVICE_ATTR(num_virtual_waves, 0660, cs40l2x_num_virtual_waves_show,
NULL);
static DEVICE_ATTR(fw_rev, 0660, cs40l2x_fw_rev_show, NULL);
static DEVICE_ATTR(fw_id, 0660, cs40l2x_fw_id_show, NULL);
static DEVICE_ATTR(fw_swap, 0660, NULL, cs40l2x_fw_swap_store);
static DEVICE_ATTR(vpp_measured, 0660, cs40l2x_vpp_measured_show, NULL);
static DEVICE_ATTR(ipp_measured, 0660, cs40l2x_ipp_measured_show, NULL);
static DEVICE_ATTR(vbatt_max, 0660, cs40l2x_vbatt_max_show,
cs40l2x_vbatt_max_store);
static DEVICE_ATTR(vbatt_min, 0660, cs40l2x_vbatt_min_show,
cs40l2x_vbatt_min_store);
static DEVICE_ATTR(exc_enable, 0660, cs40l2x_exc_enable_show,
cs40l2x_exc_enable_store);
static DEVICE_ATTR(hw_err_count, 0660, cs40l2x_hw_err_count_show,
cs40l2x_hw_err_count_store);
static DEVICE_ATTR(hw_reset, 0660, cs40l2x_hw_reset_show,
cs40l2x_hw_reset_store);
static DEVICE_ATTR(wt_file, 0660, cs40l2x_wt_file_show, cs40l2x_wt_file_store);
static DEVICE_ATTR(wt_date, 0660, cs40l2x_wt_date_show, NULL);
static DEVICE_ATTR(vmon_imon_offs_enable, 0660, cs40l2x_imon_offs_enable_show,
cs40l2x_imon_offs_enable_store);
static DEVICE_ATTR(clab_enable, 0660, cs40l2x_clab_enable_show,
cs40l2x_clab_enable_store);
static DEVICE_ATTR(clab_peak, 0660, cs40l2x_clab_peak_show,
cs40l2x_clab_peak_store);
static DEVICE_ATTR(pwle_regulation_enable, 0660, cs40l2x_par_enable_show,
cs40l2x_par_enable_store);
static DEVICE_ATTR(gain_compensation_enable, 0660, cs40l2x_par_gain_comp_show,
cs40l2x_par_gain_comp_store);
static DEVICE_ATTR(vibe_state, 0660, cs40l2x_vibe_state_show, NULL);
static DEVICE_ATTR(gpio_event, 0660, cs40l2x_gpio_event_show,
cs40l2x_gpio_event_store);
static DEVICE_ATTR(safe_save_state, 0660, cs40l2x_safe_save_state_show, NULL);
static DEVICE_ATTR(max_back_emf, 0660, cs40l2x_max_back_emf_show,
cs40l2x_max_back_emf_store);
static DEVICE_ATTR(autosuspend_delay, 0660, cs40l2x_autosuspend_delay_show,
cs40l2x_autosuspend_delay_store);
static DEVICE_ATTR(pwle, 0660, cs40l2x_pwle_show, cs40l2x_pwle_store);
static DEVICE_ATTR(num_virtual_composite, 0660,
cs40l2x_num_virtual_composite_show, NULL);
static DEVICE_ATTR(num_virtual_pwle, 0660,
cs40l2x_num_virtual_pwle_show, NULL);
static DEVICE_ATTR(virtual_composite_indexes, 0660,
cs40l2x_composite_indexes_show, NULL);
static DEVICE_ATTR(virtual_pwle_indexes, 0660,
cs40l2x_pwle_indexes_show, NULL);
static DEVICE_ATTR(available_pwle_segments, 0660,
cs40l2x_available_pwle_segs_show, NULL);
static DEVICE_ATTR(boost_ipk, 0660, cs40l2x_boost_ipk_show, cs40l2x_boost_ipk_store);
static struct attribute *cs40l2x_dev_attrs[] = {
&dev_attr_cp_trigger_index.attr,
&dev_attr_cp_trigger_queue.attr,
&dev_attr_cp_trigger_duration.attr,
&dev_attr_cp_trigger_q_sub.attr,
&dev_attr_hiber_cmd.attr,
&dev_attr_hiber_timeout.attr,
&dev_attr_gpio1_enable.attr,
&dev_attr_gpio1_rise_index.attr,
&dev_attr_gpio1_fall_index.attr,
&dev_attr_gpio1_fall_timeout.attr,
&dev_attr_gpio2_rise_index.attr,
&dev_attr_gpio2_fall_index.attr,
&dev_attr_gpio3_rise_index.attr,
&dev_attr_gpio3_fall_index.attr,
&dev_attr_gpio4_rise_index.attr,
&dev_attr_gpio4_fall_index.attr,
&dev_attr_standby_timeout.attr,
&dev_attr_f0_measured.attr,
&dev_attr_f0_stored.attr,
&dev_attr_bemf_measured.attr,
&dev_attr_bemf_rec.attr,
&dev_attr_bemf_rec_en.attr,
&dev_attr_bemf_shift.attr,
&dev_attr_dynamic_f0.attr,
&dev_attr_dynamic_f0_index.attr,
&dev_attr_dynamic_f0_val.attr,
&dev_attr_f0_offset.attr,
&dev_attr_redc_measured.attr,
&dev_attr_redc_stored.attr,
&dev_attr_q_measured.attr,
&dev_attr_q_stored.attr,
&dev_attr_comp_enable.attr,
&dev_attr_redc_comp_enable.attr,
&dev_attr_dig_scale.attr,
&dev_attr_gpio1_dig_scale.attr,
&dev_attr_gpio1_rise_dig_scale.attr,
&dev_attr_gpio1_fall_dig_scale.attr,
&dev_attr_gpio2_rise_dig_scale.attr,
&dev_attr_gpio2_fall_dig_scale.attr,
&dev_attr_gpio3_rise_dig_scale.attr,
&dev_attr_gpio3_fall_dig_scale.attr,
&dev_attr_gpio4_rise_dig_scale.attr,
&dev_attr_gpio4_fall_dig_scale.attr,
&dev_attr_cp_dig_scale.attr,
&dev_attr_heartbeat.attr,
&dev_attr_num_waves.attr,
&dev_attr_num_virtual_waves.attr,
&dev_attr_fw_rev.attr,
&dev_attr_fw_id.attr,
&dev_attr_fw_swap.attr,
&dev_attr_vpp_measured.attr,
&dev_attr_ipp_measured.attr,
&dev_attr_vbatt_max.attr,
&dev_attr_vbatt_min.attr,
&dev_attr_exc_enable.attr,
&dev_attr_hw_err_count.attr,
&dev_attr_hw_reset.attr,
&dev_attr_wt_file.attr,
&dev_attr_wt_date.attr,
&dev_attr_vmon_imon_offs_enable.attr,
&dev_attr_clab_enable.attr,
&dev_attr_clab_peak.attr,
&dev_attr_pwle_regulation_enable.attr,
&dev_attr_gain_compensation_enable.attr,
&dev_attr_vibe_state.attr,
&dev_attr_gpio_event.attr,
&dev_attr_safe_save_state.attr,
&dev_attr_max_back_emf.attr,
&dev_attr_autosuspend_delay.attr,
&dev_attr_pwle.attr,
&dev_attr_num_virtual_composite.attr,
&dev_attr_num_virtual_pwle.attr,
&dev_attr_virtual_composite_indexes.attr,
&dev_attr_virtual_pwle_indexes.attr,
&dev_attr_available_pwle_segments.attr,
&dev_attr_boost_ipk.attr,
&dev_attr_pwle_ramp_down.attr,
NULL,
};
static struct attribute_group cs40l2x_dev_attr_group = {
.attrs = cs40l2x_dev_attrs,
};
static void cs40l2x_wl_apply(struct cs40l2x_private *cs40l2x)
{
struct device *dev = cs40l2x->dev;
pm_runtime_get_sync(cs40l2x->dev);
pm_stay_awake(dev);
dev_dbg(dev, "Applied suspend blocker\n");
}
static void cs40l2x_wl_relax(struct cs40l2x_private *cs40l2x)
{
struct device *dev = cs40l2x->dev;
pm_relax(dev);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
dev_dbg(dev, "Relaxed suspend blocker\n");
}
static int cs40l2x_read_dyn_f0_table(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int enable = 0, reg, data[CS40l2X_F0_MAX_ENTRIES];
int ret, i, j = 0;
reg = cs40l2x_dsp_reg(cs40l2x, "DYNAMIC_F0_ENABLED",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_DYN_F0);
if (reg) {
ret = regmap_read(regmap, reg, &enable);
if (ret)
return ret;
}
if (!enable)
return 0;
memset(&data[0], 0, sizeof(data));
ret = regmap_bulk_read(regmap, cs40l2x_dsp_reg(cs40l2x, "DYN_F0_TABLE",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_DYN_F0), &data[0],
CS40l2X_F0_MAX_ENTRIES);
if (ret)
return ret;
for (i = 0; i < CS40l2X_F0_MAX_ENTRIES; i++) {
dev_dbg(dev, "%d dyn f0 entry 0x%x\n", i,
data[i]);
if (data[i] == CS40L2X_DYN_F0_DEFAULT)
continue;
cs40l2x->dynamic_f0[j].index =
data[i] >> CS40L2X_DYN_F0_INDEX_SHIFT;
cs40l2x->dynamic_f0[j].f0 = data[i] & CS40L2X_DYN_F0_MASK;
cs40l2x->dynamic_f0[j++].changed = true;
}
return 0;
}
static int cs40l2x_enable_classh(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
int ret, i;
for (i = 0; i < CS40L2X_MAX_WAVEFORMS; i++)
if (cs40l2x->clab_wt_en[i] || cs40l2x->f0_wt_en[i])
break;
if (i == CS40L2X_MAX_WAVEFORMS)
return 0;
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_VCTRL2,
CS40L2X_BST_CTL_SEL_MASK,
CS40L2X_BST_CTL_SEL_CLASSH);
if (ret)
return ret;
ret = regmap_update_bits(regmap, CS40L2X_PWR_CTRL3,
CS40L2X_CLASSH_EN_MASK,
1 << CS40L2X_CLASSH_EN_SHIFT);
if (ret)
return ret;
return 0;
}
static void cs40l2x_vibe_mode_worker(struct work_struct *work)
{
struct cs40l2x_private *cs40l2x =
container_of(work, struct cs40l2x_private, vibe_mode_work);
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int val;
int ret;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = regmap_read(regmap, cs40l2x_dsp_reg(cs40l2x, "STATUS",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_VIBE), &val);
if (ret) {
dev_err(dev, "Failed to capture playback status\n");
goto err_exit;
}
if (val != CS40L2X_STATUS_IDLE)
goto err_exit;
if (cs40l2x->a2h_enable) {
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_VCTRL2,
CS40L2X_BST_CTL_SEL_MASK,
CS40L2X_BST_CTL_SEL_CLASSH);
if (ret)
goto err_exit;
ret = regmap_update_bits(regmap, CS40L2X_PWR_CTRL3,
CS40L2X_CLASSH_EN_MASK,
1 << CS40L2X_CLASSH_EN_SHIFT);
if (ret)
goto err_exit;
ret = cs40l2x_ack_write(cs40l2x, CS40L2X_MBOX_USER_CONTROL,
CS40L2X_A2H_I2S_START, CS40L2X_USER_CTRL_SUCCESS);
if (ret)
goto err_exit;
} else {
/* haptic-mode teardown */
if (cs40l2x->vibe_state == CS40L2X_VIBE_STATE_STOPPED
|| cs40l2x->pbq_state != CS40L2X_PBQ_STATE_IDLE)
goto err_exit;
ret = cs40l2x_ground_amp(cs40l2x, true);
if (ret) {
dev_err(dev, "Failed to ground amplifier outputs\n");
goto err_exit;
}
}
cs40l2x_set_state(cs40l2x, CS40L2X_VIBE_STATE_STOPPED);
cs40l2x_wl_relax(cs40l2x);
if (cs40l2x->dyn_f0_enable) {
ret = cs40l2x_read_dyn_f0_table(cs40l2x);
if (ret) {
dev_err(dev, "Failed to read f0 table %d\n", ret);
goto err_exit;
}
}
if (cs40l2x->cond_class_h_en) {
ret = cs40l2x_enable_classh(cs40l2x);
if (ret)
goto err_exit;
}
err_exit:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
}
static enum hrtimer_restart cs40l2x_asp_timer(struct hrtimer *timer)
{
struct cs40l2x_private *cs40l2x =
container_of(timer, struct cs40l2x_private, asp_timer);
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_mode_work);
return HRTIMER_NORESTART;
}
static int cs40l2x_stop_playback(struct cs40l2x_private *cs40l2x)
{
int ret, i;
for (i = 0; i < CS40L2X_ENDPLAYBACK_RETRIES; i++) {
ret = regmap_write(cs40l2x->regmap,
cs40l2x_dsp_reg(cs40l2x, "ENDPLAYBACK",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
CS40L2X_ENDPLAYBACK_REQ);
if (!ret)
return 0;
usleep_range(10000, 10100);
}
dev_err(cs40l2x->dev, "Parking device in reset\n");
gpiod_set_value_cansleep(cs40l2x->reset_gpio, 0);
return -EIO;
}
static int cs40l2x_pbq_cancel(struct cs40l2x_private *cs40l2x)
{
int ret;
hrtimer_cancel(&cs40l2x->pbq_timer);
switch (cs40l2x->pbq_state) {
case CS40L2X_PBQ_STATE_SILENT:
case CS40L2X_PBQ_STATE_IDLE:
ret = cs40l2x_cp_dig_scale_set(cs40l2x,
cs40l2x->pbq_cp_dig_scale);
if (ret)
return ret;
cs40l2x_set_state(cs40l2x, CS40L2X_VIBE_STATE_STOPPED);
cs40l2x_wl_relax(cs40l2x);
break;
case CS40L2X_PBQ_STATE_PLAYING:
ret = cs40l2x_stop_playback(cs40l2x);
if (ret)
return ret;
ret = cs40l2x_cp_dig_scale_set(cs40l2x,
cs40l2x->pbq_cp_dig_scale);
if (ret)
return ret;
cs40l2x_set_state(cs40l2x, CS40L2X_VIBE_STATE_STOPPED);
cs40l2x_wl_relax(cs40l2x);
break;
default:
return -EINVAL;
}
cs40l2x->pbq_state = CS40L2X_PBQ_STATE_IDLE;
cs40l2x->cp_trailer_index = CS40L2X_INDEX_IDLE;
return 0;
}
static int cs40l2x_pbq_play(struct cs40l2x_private *cs40l2x,
struct wt_type10_comp_section *section)
{
unsigned int cp_dig_scale = cs40l2x->pbq_cp_dig_scale;
int ret;
cp_dig_scale += cs40l2x_pbq_dig_scale[section->amplitude];
clamp_t(unsigned int, cp_dig_scale, 0, CS40L2X_DIG_SCALE_MAX);
ret = cs40l2x_cp_dig_scale_set(cs40l2x, cp_dig_scale);
if (ret)
return ret;
ret = cs40l2x_ground_amp(cs40l2x, false);
if (ret)
return ret;
ret = cs40l2x_ack_write(cs40l2x, CS40L2X_MBOX_TRIGGERINDEX,
section->index, CS40L2X_MBOX_TRIGGERRESET);
if (ret)
return ret;
cs40l2x->pbq_state = CS40L2X_PBQ_STATE_PLAYING;
if (cs40l2x->event_control & CS40L2X_EVENT_END_ENABLED)
return 0;
hrtimer_start(&cs40l2x->pbq_timer, ktime_set(0, CS40L2X_PBQ_POLL_NS),
HRTIMER_MODE_REL);
return 0;
}
static int cs40l2x_pbq_pair_launch(struct cs40l2x_private *cs40l2x)
{
struct wt_type10_comp_section *section;
int ret;
while (cs40l2x->pbq_index < (cs40l2x->pbq_comp.nsections << 1)) {
section = &cs40l2x->pbq_comp.sections[cs40l2x->pbq_index >> 1];
if (!(cs40l2x->pbq_index & 0x1)) {
cs40l2x->pbq_index++;
if (section->amplitude)
return cs40l2x_pbq_play(cs40l2x, section);
}
if (section->delay) {
ret = cs40l2x_ground_amp(cs40l2x, true);
if (ret)
return ret;
hrtimer_start(&cs40l2x->pbq_timer,
ktime_set(section->delay / 1000,
(section->delay % 1000) *
1000000),
HRTIMER_MODE_REL);
cs40l2x->pbq_state = CS40L2X_PBQ_STATE_SILENT;
}
/* Handle inner loops */
if (section->repeat == WT_REPEAT_LOOP_MARKER) {
cs40l2x->pbq_inner_mark = cs40l2x->pbq_index & ~0x1;
} else if (section->repeat) {
if (++cs40l2x->pbq_inner_loop <= section->repeat) {
if (cs40l2x->pbq_inner_mark >= 0)
cs40l2x->pbq_index = cs40l2x->pbq_inner_mark;
else
cs40l2x->pbq_index &= ~0x1;
continue;
}
cs40l2x->pbq_inner_mark = -1;
cs40l2x->pbq_inner_loop = 0;
}
/* Handle outer loops */
if (++cs40l2x->pbq_index == (cs40l2x->pbq_comp.nsections << 1)) {
if (cs40l2x->pbq_comp.repeat != WT_REPEAT_LOOP_MARKER)
cs40l2x->pbq_outer_loop++;
if (cs40l2x->pbq_outer_loop > cs40l2x->pbq_comp.repeat)
cs40l2x->pbq_outer_loop = 0;
else
cs40l2x->pbq_index = 0;
}
if (section->delay)
return 0;
}
cs40l2x->pbq_state = CS40L2X_PBQ_STATE_IDLE;
return cs40l2x_pbq_cancel(cs40l2x);
}
static void cs40l2x_vibe_pbq_worker(struct work_struct *work)
{
struct cs40l2x_private *cs40l2x =
container_of(work, struct cs40l2x_private, vibe_pbq_work);
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int val;
int ret;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
switch (cs40l2x->pbq_state) {
case CS40L2X_PBQ_STATE_IDLE:
goto err_exit;
case CS40L2X_PBQ_STATE_PLAYING:
if (cs40l2x->event_control & CS40L2X_EVENT_END_ENABLED)
break;
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "STATUS",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE),
&val);
if (ret) {
dev_err(dev, "Failed to capture playback status\n");
goto err_exit;
}
if (val != CS40L2X_STATUS_IDLE) {
hrtimer_start(&cs40l2x->pbq_timer,
ktime_set(0, CS40L2X_PBQ_POLL_NS),
HRTIMER_MODE_REL);
goto err_exit;
}
break;
case CS40L2X_PBQ_STATE_SILENT:
break;
default:
dev_err(dev, "Unexpected playback queue state: %d\n",
cs40l2x->pbq_state);
goto err_exit;
}
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "STATUS",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE),
&val);
if (ret) {
dev_err(dev, "Failed to capture playback status\n");
goto err_exit;
}
if (val != CS40L2X_STATUS_IDLE)
goto err_exit;
ret = cs40l2x_pbq_pair_launch(cs40l2x);
if (ret)
dev_err(dev, "Failed to continue playback queue\n");
err_exit:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
}
static enum hrtimer_restart cs40l2x_pbq_timer(struct hrtimer *timer)
{
struct cs40l2x_private *cs40l2x =
container_of(timer, struct cs40l2x_private, pbq_timer);
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_pbq_work);
return HRTIMER_NORESTART;
}
static int cs40l2x_diag_enable(struct cs40l2x_private *cs40l2x,
unsigned int val)
{
struct regmap *regmap = cs40l2x->regmap;
switch (cs40l2x->fw_desc->id) {
case CS40L2X_FW_ID_ORIG:
/*
* STIMULUS_MODE is not automatically returned to a reset
* value as with other mailbox registers, therefore it is
* written without polling for subsequent acknowledgment
*/
return regmap_write(regmap, CS40L2X_MBOX_STIMULUS_MODE, val);
case CS40L2X_FW_ID_CAL:
return regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "F0_TRACKING_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_F0), val);
default:
return -EPERM;
}
}
static int cs40l2x_diag_capture(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
unsigned int val, reg;
int ret;
switch (cs40l2x->diag_state) {
case CS40L2X_DIAG_STATE_RUN1:
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "F0",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_F0),
&cs40l2x->f0_measured);
if (ret)
return ret;
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "REDC",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_F0),
&cs40l2x->redc_measured);
if (ret)
return ret;
reg = cs40l2x_dsp_reg(cs40l2x, "MAXBACKEMF",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_F0);
if (reg) {
ret = regmap_read(regmap, reg, &cs40l2x->bemf_measured);
if (ret)
return ret;
}
cs40l2x->diag_state = CS40L2X_DIAG_STATE_DONE1;
return 0;
case CS40L2X_DIAG_STATE_RUN2:
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "F0_TRACKING_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_F0),
&val);
if (ret)
return ret;
if (val != CS40L2X_F0_TRACKING_OFF)
return -EBUSY;
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "Q_EST",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_QEST),
&val);
if (ret)
return ret;
if (val & CS40L2X_QEST_ERROR)
return -EIO;
cs40l2x->q_measured = val;
cs40l2x->diag_state = CS40L2X_DIAG_STATE_DONE2;
return 0;
default:
return -EINVAL;
}
}
static int cs40l2x_peak_capture(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
unsigned int vmon_max, vmon_min, imon_max, imon_min;
int ret;
/* VMON min and max are returned as 24-bit two's-complement values */
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "VMONMAX",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
&vmon_max);
if (ret)
return ret;
if (vmon_max > CS40L2X_VMON_POSFS)
vmon_max = ((vmon_max ^ CS40L2X_VMON_MASK) + 1) * -1;
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "VMONMIN",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
&vmon_min);
if (ret)
return ret;
if (vmon_min > CS40L2X_VMON_POSFS)
vmon_min = ((vmon_min ^ CS40L2X_VMON_MASK) + 1) * -1;
/* IMON min and max are returned as 24-bit two's-complement values */
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "IMONMAX",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
&imon_max);
if (ret)
return ret;
if (imon_max > CS40L2X_IMON_POSFS)
imon_max = ((imon_max ^ CS40L2X_IMON_MASK) + 1) * -1;
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "IMONMIN",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
&imon_min);
if (ret)
return ret;
if (imon_min > CS40L2X_IMON_POSFS)
imon_min = ((imon_min ^ CS40L2X_IMON_MASK) + 1) * -1;
cs40l2x->vpp_measured = vmon_max - vmon_min;
cs40l2x->ipp_measured = imon_max - imon_min;
return 0;
}
static int cs40l2x_reset_recovery(struct cs40l2x_private *cs40l2x)
{
bool wl_pending = (cs40l2x->vibe_state == CS40L2X_VIBE_STATE_RUNNING);
unsigned int fw_id_restore;
int ret, i;
if (cs40l2x->revid < CS40L2X_REVID_B1)
return -EPERM;
if (cs40l2x->asp_available) {
ret = cs40l2x_wseq_replace(cs40l2x, CS40L2X_PLL_CLK_CTRL,
((1 << CS40L2X_PLL_REFCLK_EN_SHIFT)
& CS40L2X_PLL_REFCLK_EN_MASK) |
((CS40L2X_PLL_REFCLK_SEL_MCLK
<< CS40L2X_PLL_REFCLK_SEL_SHIFT)
& CS40L2X_PLL_REFCLK_SEL_MASK));
if (ret)
return ret;
}
cs40l2x_set_state(cs40l2x, CS40L2X_VIBE_STATE_STOPPED);
cs40l2x->cp_trailer_index = CS40L2X_INDEX_IDLE;
gpiod_set_value_cansleep(cs40l2x->reset_gpio, 0);
usleep_range(2000, 2100);
gpiod_set_value_cansleep(cs40l2x->reset_gpio, 1);
usleep_range(1000, 1100);
fw_id_restore = cs40l2x->fw_desc->id;
cs40l2x->fw_desc = cs40l2x_firmware_match(cs40l2x, CS40L2X_FW_ID_B1ROM);
ret = cs40l2x_firmware_swap(cs40l2x, fw_id_restore);
if (ret)
return ret;
for (i = 0; i < cs40l2x->dsp_cache_depth; i++) {
ret = regmap_write(cs40l2x->regmap,
cs40l2x->dsp_cache[i].reg,
cs40l2x->dsp_cache[i].val);
if (ret) {
dev_err(cs40l2x->dev, "Failed to restore DSP cache\n");
return ret;
}
}
if (cs40l2x->pbq_state != CS40L2X_PBQ_STATE_IDLE) {
ret = cs40l2x_cp_dig_scale_set(cs40l2x,
cs40l2x->pbq_cp_dig_scale);
if (ret)
return ret;
cs40l2x->pbq_state = CS40L2X_PBQ_STATE_IDLE;
}
if (wl_pending)
cs40l2x_wl_relax(cs40l2x);
dev_info(cs40l2x->dev, "Successfully restored device state\n");
return 0;
}
static int cs40l2x_check_recovery(struct cs40l2x_private *cs40l2x)
{
struct i2c_client *i2c_client = to_i2c_client(cs40l2x->dev);
unsigned int val;
int ret;
ret = regmap_read(cs40l2x->regmap, CS40L2X_DSP1RX2_INPUT, &val);
if (ret) {
dev_err(cs40l2x->dev, "Failed to read known register\n");
return ret;
}
if (val == CS40L2X_DSP1_RXn_SRC_VMON)
return 0;
dev_err(cs40l2x->dev, "Failed to verify known register\n");
/*
* resetting the device prompts it to briefly assert the /ALERT pin,
* so disable the interrupt line until the device has been restored
*/
disable_irq_nosync(i2c_client->irq);
ret = cs40l2x_reset_recovery(cs40l2x);
enable_irq(i2c_client->irq);
return ret;
}
static int cs40l2x_classh_wt_check(struct cs40l2x_private *cs40l2x,
const unsigned char *data,
const int len, int *pos)
{
struct device *dev = cs40l2x->dev;
int i, index;
unsigned int header_end = CS40L2X_WT_HEAD_END;
if (*pos < 0 || *pos >= CS40L2X_MAX_WAVEFORMS)
return -EINVAL;
index = *pos;
/* Check the wave table header for CLAB and F0 waveforms */
for (i = 1; i < len; i += CS40L2X_WT_DESC_BYTE_OFFSET) {
if (!memcmp(&header_end, (data + i), 3))
break;
if (*(data + i) & CS40L2X_CLAB_WT_EN)
cs40l2x->clab_wt_en[index] = true;
if (*(data + i) & CS40L2X_F0_WT_EN)
cs40l2x->f0_wt_en[index] = true;
dev_dbg(dev, "header = 0x%x clab_wt_en = 0x%x\n", *(data + i),
cs40l2x->clab_wt_en[index]);
index++;
if (index >= CS40L2X_MAX_WAVEFORMS) {
dev_err(dev, "Overflow on waveforms\n");
return -EFAULT;
}
}
*pos += index;
return 0;
}
static int cs40l2x_set_boost_voltage(struct cs40l2x_private *cs40l2x,
unsigned int boost_ctl)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int bst_ctl_scaled;
int ret;
if (boost_ctl)
boost_ctl &= CS40L2X_PDATA_MASK;
else
boost_ctl = CS40L2X_BST_VOLT_MAX;
switch (boost_ctl) {
case 0:
bst_ctl_scaled = boost_ctl;
break;
case CS40L2X_BST_VOLT_MIN ... CS40L2X_BST_VOLT_MAX:
bst_ctl_scaled = ((boost_ctl - CS40L2X_BST_VOLT_MIN) / 50) + 1;
break;
default:
dev_err(dev, "Invalid VBST limit: %d mV\n", boost_ctl);
return -EINVAL;
}
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_VCTRL1,
CS40L2X_BST_CTL_MASK,
bst_ctl_scaled << CS40L2X_BST_CTL_SHIFT);
if (ret) {
dev_err(dev, "Failed to write VBST limit\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_VCTRL2,
CS40L2X_BST_CTL_LIM_EN_MASK,
1 << CS40L2X_BST_CTL_LIM_EN_SHIFT);
if (ret) {
dev_err(dev, "Failed to configure VBST control\n");
return ret;
}
return 0;
}
static int cs40l2x_cond_classh(struct cs40l2x_private *cs40l2x, int index)
{
struct regmap *regmap = cs40l2x->regmap;
unsigned int enable = 0, reg, boost = cs40l2x->pdata.boost_ctl;
int ret = 0;
bool disable_classh = false;
if (index < 0 || index >= CS40L2X_MAX_WAVEFORMS)
return -EINVAL;
if (cs40l2x->dyn_f0_enable) {
reg = cs40l2x_dsp_reg(cs40l2x, "DYNAMIC_F0_ENABLED",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_DYN_F0);
if (reg) {
ret = regmap_read(regmap, reg, &enable);
if (ret)
return ret;
}
}
if (enable) {
if (cs40l2x->f0_wt_en[index]) {
boost = cs40l2x->pdata.boost_ctl;
disable_classh = true;
}
}
reg = cs40l2x_dsp_reg(cs40l2x, "CLAB_ENABLED",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_CLAB);
if (reg) {
ret = regmap_read(regmap, reg, &enable);
if (ret)
return ret;
if (enable) {
if (cs40l2x->clab_wt_en[index]) {
boost = cs40l2x->pdata.boost_clab;
disable_classh = true;
}
}
}
if (disable_classh) {
ret = cs40l2x_set_boost_voltage(cs40l2x, boost);
if (ret)
return ret;
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_VCTRL2,
CS40L2X_BST_CTL_SEL_MASK,
CS40L2X_BST_CTL_SEL_CP_VAL);
if (ret)
return ret;
ret = regmap_update_bits(regmap, CS40L2X_PWR_CTRL3,
CS40L2X_CLASSH_EN_MASK,
0 << CS40L2X_CLASSH_EN_SHIFT);
if (ret)
return ret;
} else {
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_VCTRL2,
CS40L2X_BST_CTL_SEL_MASK,
CS40L2X_BST_CTL_SEL_CLASSH);
if (ret)
return ret;
ret = regmap_update_bits(regmap, CS40L2X_PWR_CTRL3,
CS40L2X_CLASSH_EN_MASK,
1 << CS40L2X_CLASSH_EN_SHIFT);
if (ret)
return ret;
ret = cs40l2x_set_boost_voltage(cs40l2x, boost);
if (ret)
return ret;
}
return 0;
}
static void cs40l2x_vibe_start_worker(struct work_struct *work)
{
struct cs40l2x_private *cs40l2x =
container_of(work, struct cs40l2x_private, vibe_start_work);
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
int ret, i;
unsigned int reg;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
#ifdef CONFIG_HAPTICS_CS40L2X_INPUT
if (cs40l2x->effect) {
i = cs40l2x->trigger_indices[cs40l2x->effect->id];
cs40l2x_cp_trigger_index_impl(cs40l2x, i);
cs40l2x->effect = NULL;
}
#endif
if (!cs40l2x->virtual_stored) {
dev_warn(dev, "Unsafe condition encountered.\n");
goto err_mutex;
}
cs40l2x_set_safe_save_state(cs40l2x, CS40L2X_SAVE_UNSAFE);
/* handle interruption of special cases */
switch (cs40l2x->cp_trailer_index) {
case CS40L2X_INDEX_QEST:
case CS40L2X_INDEX_PEAK:
case CS40L2X_INDEX_DIAG:
dev_err(dev, "Ignored attempt to interrupt measurement\n");
goto err_mutex;
case CS40L2X_INDEX_PBQ:
dev_err(dev, "Ignored attempt to interrupt playback queue\n");
goto err_mutex;
}
for (i = 0; i < CS40L2X_NUM_HW_ERRS; i++)
if (cs40l2x->hw_err_count[i] > CS40L2X_HW_ERR_COUNT_MAX)
dev_warn(dev, "Pending %s error\n",
cs40l2x_hw_errs[i].err_name);
else
cs40l2x->hw_err_count[i] = 0;
if (cs40l2x->pdata.auto_recovery) {
ret = cs40l2x_check_recovery(cs40l2x);
if (ret)
goto err_mutex;
}
if (cs40l2x->cp_trigger_index == CS40L2X_INDEX_QEST
&& cs40l2x->diag_state < CS40L2X_DIAG_STATE_DONE1) {
dev_err(dev, "Diagnostics index (%d) not yet administered\n",
CS40L2X_INDEX_DIAG);
cs40l2x->cp_trailer_index = CS40L2X_INDEX_IDLE;
goto err_mutex;
} else {
cs40l2x->cp_trailer_index = cs40l2x->cp_trigger_index;
}
switch (cs40l2x->cp_trailer_index) {
case CS40L2X_INDEX_DIAG:
reg = cs40l2x_dsp_reg(cs40l2x, "MAXBACKEMF",
CS40L2X_XM_UNPACKED_TYPE, CS40L2X_ALGO_ID_F0);
if (reg) {
ret = regmap_write(regmap, reg, 0);
if (ret)
goto err_mutex;
}
fallthrough;
case CS40L2X_INDEX_VIBE:
case CS40L2X_INDEX_CONT_MIN ... CS40L2X_INDEX_CONT_MAX:
case CS40L2X_INDEX_QEST:
case CS40L2X_INDEX_PEAK:
#ifdef CONFIG_ANDROID_TIMED_OUTPUT
hrtimer_start(&cs40l2x->vibe_timer,
ktime_set(cs40l2x->vibe_timeout / 1000,
(cs40l2x->vibe_timeout % 1000)
* 1000000),
HRTIMER_MODE_REL);
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
fallthrough;
case CS40L2X_INDEX_PBQ:
if (cs40l2x->vibe_state != CS40L2X_VIBE_STATE_RUNNING)
cs40l2x_wl_apply(cs40l2x);
cs40l2x_set_state(cs40l2x, CS40L2X_VIBE_STATE_RUNNING);
break;
case CS40L2X_INDEX_CLICK_MIN ... CS40L2X_INDEX_CLICK_MAX:
if (!(cs40l2x->event_control & CS40L2X_EVENT_END_ENABLED))
break;
if (cs40l2x->vibe_state != CS40L2X_VIBE_STATE_RUNNING)
cs40l2x_wl_apply(cs40l2x);
cs40l2x_set_state(cs40l2x, CS40L2X_VIBE_STATE_RUNNING);
break;
}
if (cs40l2x->cp_trailer_index != CS40L2X_INDEX_PBQ) {
ret = cs40l2x_ground_amp(cs40l2x, false);
if (ret) {
dev_err(dev, "Failed to free amplifier outputs\n");
goto err_relax;
}
}
switch (cs40l2x->cp_trailer_index) {
case CS40L2X_INDEX_PEAK:
cs40l2x->vpp_measured = -1;
cs40l2x->ipp_measured = -1;
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "GPIO_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
&cs40l2x->peak_gpio1_enable);
if (ret) {
dev_err(dev, "Failed to read GPIO1 configuration\n");
break;
}
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "GPIO_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
CS40L2X_GPIO1_DISABLED);
if (ret) {
dev_err(dev, "Failed to disable GPIO1\n");
break;
}
ret = cs40l2x_ack_write(cs40l2x, CS40L2X_MBOX_TRIGGER_MS,
CS40L2X_INDEX_VIBE, CS40L2X_MBOX_TRIGGERRESET);
if (ret)
break;
msleep(CS40L2X_PEAK_DELAY_MS);
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "VMONMAX",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
CS40L2X_VMON_NEGFS);
if (ret) {
dev_err(dev, "Failed to reset maximum VMON\n");
break;
}
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "VMONMIN",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
CS40L2X_VMON_POSFS);
if (ret) {
dev_err(dev, "Failed to reset minimum VMON\n");
break;
}
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "IMONMAX",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
CS40L2X_IMON_NEGFS);
if (ret) {
dev_err(dev, "Failed to reset maximum IMON\n");
break;
}
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "IMONMIN",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
CS40L2X_IMON_POSFS);
if (ret)
dev_err(dev, "Failed to reset minimum IMON\n");
break;
case CS40L2X_INDEX_VIBE:
case CS40L2X_INDEX_CONT_MIN ... CS40L2X_INDEX_CONT_MAX:
if (completion_done(&cs40l2x->hap_done))
reinit_completion(&cs40l2x->hap_done);
if (cs40l2x->cond_class_h_en) {
ret = cs40l2x_cond_classh(cs40l2x,
cs40l2x->cp_trailer_index & CS40L2X_INDEX_MASK);
if (ret) {
dev_err(dev, "Conditional ClassH failed\n");
break;
}
}
ret = cs40l2x_ack_write(cs40l2x, CS40L2X_MBOX_TRIGGER_MS,
cs40l2x->cp_trailer_index & CS40L2X_INDEX_MASK,
CS40L2X_MBOX_TRIGGERRESET);
break;
case CS40L2X_INDEX_CLICK_MIN ... CS40L2X_INDEX_CLICK_MAX:
if (completion_done(&cs40l2x->hap_done))
reinit_completion(&cs40l2x->hap_done);
if (cs40l2x->cond_class_h_en) {
ret = cs40l2x_cond_classh(cs40l2x,
cs40l2x->cp_trailer_index);
if (ret) {
dev_err(dev, "Conditional ClassH failed\n");
break;
}
}
ret = cs40l2x_ack_write(cs40l2x, CS40L2X_MBOX_TRIGGERINDEX,
cs40l2x->cp_trailer_index,
CS40L2X_MBOX_TRIGGERRESET);
break;
case CS40L2X_INDEX_PBQ:
cs40l2x->pbq_cp_dig_scale = CS40L2X_DIG_SCALE_RESET;
ret = cs40l2x_cp_dig_scale_get(cs40l2x,
&cs40l2x->pbq_cp_dig_scale);
if (ret) {
dev_err(dev, "Failed to read digital scale\n");
break;
}
cs40l2x->pbq_index = 0;
ret = cs40l2x_pbq_pair_launch(cs40l2x);
if (ret)
dev_err(dev, "Failed to launch playback queue\n");
break;
case CS40L2X_INDEX_DIAG:
cs40l2x->diag_state = CS40L2X_DIAG_STATE_INIT;
cs40l2x->diag_dig_scale = CS40L2X_DIG_SCALE_RESET;
ret = cs40l2x_dig_scale_get(cs40l2x, &cs40l2x->diag_dig_scale);
if (ret) {
dev_err(dev, "Failed to read digital scale\n");
break;
}
ret = cs40l2x_dig_scale_set(cs40l2x, 0);
if (ret) {
dev_err(dev, "Failed to reset digital scale\n");
break;
}
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "CLOSED_LOOP",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_F0),
0);
if (ret) {
dev_err(dev, "Failed to disable closed-loop mode\n");
break;
}
ret = cs40l2x_diag_enable(cs40l2x, CS40L2X_F0_TRACKING_DIAG);
if (ret) {
dev_err(dev, "Failed to enable diagnostics tone\n");
break;
}
msleep(CS40L2X_DIAG_STATE_DELAY_MS);
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "CLOSED_LOOP",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_F0),
1);
if (ret) {
dev_err(dev, "Failed to enable closed-loop mode\n");
break;
}
cs40l2x->diag_state = CS40L2X_DIAG_STATE_RUN1;
break;
case CS40L2X_INDEX_QEST:
cs40l2x->diag_dig_scale = CS40L2X_DIG_SCALE_RESET;
ret = cs40l2x_dig_scale_get(cs40l2x, &cs40l2x->diag_dig_scale);
if (ret) {
dev_err(dev, "Failed to read digital scale\n");
break;
}
ret = cs40l2x_dig_scale_set(cs40l2x, 0);
if (ret) {
dev_err(dev, "Failed to reset digital scale\n");
break;
}
ret = cs40l2x_diag_enable(cs40l2x, CS40L2X_F0_TRACKING_QEST);
if (ret) {
dev_err(dev, "Failed to enable diagnostics tone\n");
break;
}
cs40l2x->diag_state = CS40L2X_DIAG_STATE_RUN2;
break;
default:
ret = -EINVAL;
dev_err(dev, "Invalid wavetable index\n");
}
err_relax:
if (cs40l2x->vibe_state == CS40L2X_VIBE_STATE_STOPPED)
goto err_mutex;
if (ret) {
cs40l2x_set_state(cs40l2x, CS40L2X_VIBE_STATE_STOPPED);
cs40l2x_wl_relax(cs40l2x);
}
err_mutex:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
}
static void cs40l2x_vibe_stop_worker(struct work_struct *work)
{
struct cs40l2x_private *cs40l2x =
container_of(work, struct cs40l2x_private, vibe_stop_work);
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
int ret;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
if (!cs40l2x->virtual_stored) {
dev_warn(dev, "Unsafe condition encountered.\n");
return;
}
switch (cs40l2x->cp_trailer_index) {
case CS40L2X_INDEX_PEAK:
ret = cs40l2x_peak_capture(cs40l2x);
if (ret)
dev_err(dev, "Failed to capture peak-to-peak values\n");
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "GPIO_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
cs40l2x->peak_gpio1_enable);
if (ret)
dev_err(dev, "Failed to restore GPIO1 configuration\n");
fallthrough;
case CS40L2X_INDEX_VIBE:
case CS40L2X_INDEX_CONT_MIN ... CS40L2X_INDEX_CONT_MAX:
ret = cs40l2x_stop_playback(cs40l2x);
if (ret)
dev_err(dev, "Failed to stop playback\n");
if (cs40l2x->event_control & CS40L2X_EVENT_END_ENABLED)
break;
if (cs40l2x->vibe_state == CS40L2X_VIBE_STATE_STOPPED)
break;
cs40l2x_set_state(cs40l2x, CS40L2X_VIBE_STATE_STOPPED);
cs40l2x_wl_relax(cs40l2x);
break;
case CS40L2X_INDEX_CLICK_MIN ... CS40L2X_INDEX_CLICK_MAX:
ret = cs40l2x_stop_playback(cs40l2x);
if (ret)
dev_err(dev, "Failed to stop playback\n");
break;
case CS40L2X_INDEX_PBQ:
ret = cs40l2x_pbq_cancel(cs40l2x);
if (ret)
dev_err(dev, "Failed to cancel playback queue\n");
break;
case CS40L2X_INDEX_DIAG:
case CS40L2X_INDEX_QEST:
ret = cs40l2x_diag_capture(cs40l2x);
if (ret)
dev_err(dev, "Failed to capture measurement(s): %d\n",
ret);
ret = cs40l2x_diag_enable(cs40l2x, CS40L2X_F0_TRACKING_OFF);
if (ret)
dev_err(dev, "Failed to disable diagnostics tone\n");
ret = cs40l2x_dig_scale_set(cs40l2x, cs40l2x->diag_dig_scale);
if (ret)
dev_err(dev, "Failed to restore digital scale\n");
if (cs40l2x->vibe_state == CS40L2X_VIBE_STATE_STOPPED)
break;
cs40l2x_set_state(cs40l2x, CS40L2X_VIBE_STATE_STOPPED);
cs40l2x_wl_relax(cs40l2x);
break;
case CS40L2X_INDEX_IDLE:
break;
default:
dev_err(dev, "Invalid wavetable index\n");
}
cs40l2x->cp_trailer_index = CS40L2X_INDEX_IDLE;
cs40l2x_set_safe_save_state(cs40l2x, CS40L2X_SAVE_SAFE);
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
}
#ifdef CONFIG_HAPTICS_CS40L2X_INPUT
static int cs40l2x_playback_effect(struct input_dev *dev, int effect_id, int val)
{
struct cs40l2x_private *cs40l2x = input_get_drvdata(dev);
struct ff_effect *effect;
mutex_lock(&cs40l2x->lock);
effect = &dev->ff->effects[effect_id];
if (!effect) {
dev_err(cs40l2x->dev, "No such effect\n");
return -EINVAL;
}
cs40l2x->effect = effect;
mutex_unlock(&cs40l2x->lock);
if (val > 0)
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_start_work);
else
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_stop_work);
return 0;
}
static int cs40l2x_upload_effect(struct input_dev *dev,
struct ff_effect *effect,
struct ff_effect *old)
{
struct cs40l2x_private *cs40l2x = input_get_drvdata(dev);
unsigned int data_length;
s16 *raw_custom_data = NULL;
int ret = 0;
switch (effect->type) {
case FF_PERIODIC:
if (effect->u.periodic.waveform != FF_CUSTOM) {
dev_err(cs40l2x->dev,
"Waveform type must be FF_CUSTOM\n");
return -EINVAL;
}
if (effect->replay.length < 0 ||
effect->replay.length > CS40L2X_TIMEOUT_MS_MAX) {
dev_err(cs40l2x->dev,
"Invalid playback duration %d ms\n",
effect->replay.length);
return -EINVAL;
}
data_length = effect->u.periodic.custom_len;
raw_custom_data = kmalloc_array(data_length,
sizeof(*raw_custom_data),
GFP_KERNEL);
if (!raw_custom_data)
return -ENOMEM;
if (copy_from_user(raw_custom_data,
effect->u.periodic.custom_data,
sizeof(*raw_custom_data) * data_length)) {
dev_err(cs40l2x->dev, "Failed to get user data\n");
ret = -EFAULT;
goto err_free;
}
mutex_lock(&cs40l2x->lock);
if (raw_custom_data[1] >= cs40l2x->num_waves) {
dev_err(cs40l2x->dev, "Index out of bounds\n");
ret = -EINVAL;
goto err_free;
}
cs40l2x->trigger_indices[effect->id] = raw_custom_data[1];
mutex_unlock(&cs40l2x->lock);
break;
default:
dev_err(cs40l2x->dev, "Effect type 0x%X not supported\n",
effect->type);
ret = -EINVAL;
}
err_free:
kfree(raw_custom_data);
return ret;
}
static int cs40l2x_create_input_ff(struct cs40l2x_private *cs40l2x)
{
struct device *dev = cs40l2x->dev;
int ret;
cs40l2x->input = devm_input_allocate_device(cs40l2x->dev);
if (!cs40l2x->input)
return -ENOMEM;
cs40l2x->input->name = "cs40l25_input";
cs40l2x->input->id.product = cs40l2x->devid;
cs40l2x->input->id.version = cs40l2x->revid;
input_set_drvdata(cs40l2x->input, cs40l2x);
input_set_capability(cs40l2x->input, EV_FF, FF_PERIODIC);
input_set_capability(cs40l2x->input, EV_FF, FF_CUSTOM);
ret = input_ff_create(cs40l2x->input, FF_MAX_EFFECTS);
if (ret) {
dev_err(dev, "Failed to create FF device: %d\n", ret);
return ret;
}
/* input_ff_create() automatically sets FF_RUMBLE capabilities
* We want to restrict this to be only FF_PERIODIC
*/
__clear_bit(FF_RUMBLE, cs40l2x->input->ffbit);
cs40l2x->input->ff->upload = cs40l2x_upload_effect;
cs40l2x->input->ff->playback = cs40l2x_playback_effect;
ret = input_register_device(cs40l2x->input);
if (ret) {
dev_err(dev, "Cannot register input device: %d\n", ret);
return ret;
}
ret = sysfs_create_group(&cs40l2x->input->dev.kobj,
&cs40l2x_dev_attr_group);
if (ret)
dev_err(dev, "Failed to create sysfs group: %d\n", ret);
return ret;
}
#elif defined CONFIG_ANDROID_TIMED_OUTPUT
/* vibration callback for timed output device */
static void cs40l2x_vibe_enable(struct timed_output_dev *sdev, int timeout)
{
struct cs40l2x_private *cs40l2x =
container_of(sdev, struct cs40l2x_private, timed_dev);
if (timeout > 0) {
mutex_lock(&cs40l2x->lock);
cs40l2x->vibe_timeout = timeout;
mutex_unlock(&cs40l2x->lock);
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_start_work);
} else {
hrtimer_cancel(&cs40l2x->vibe_timer);
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_stop_work);
}
}
static int cs40l2x_vibe_get_time(struct timed_output_dev *sdev)
{
struct cs40l2x_private *cs40l2x =
container_of(sdev, struct cs40l2x_private, timed_dev);
int ret = 0;
if (hrtimer_active(&cs40l2x->vibe_timer))
ret = ktime_to_ms(hrtimer_get_remaining(&cs40l2x->vibe_timer));
return ret;
}
static enum hrtimer_restart cs40l2x_vibe_timer(struct hrtimer *timer)
{
struct cs40l2x_private *cs40l2x =
container_of(timer, struct cs40l2x_private, vibe_timer);
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_stop_work);
return HRTIMER_NORESTART;
}
static int cs40l2x_create_timed_output(struct cs40l2x_private *cs40l2x)
{
int ret;
struct timed_output_dev *timed_dev = &cs40l2x->timed_dev;
struct hrtimer *vibe_timer = &cs40l2x->vibe_timer;
struct device *dev = cs40l2x->dev;
timed_dev->name = CS40L2X_DEVICE_NAME;
timed_dev->enable = cs40l2x_vibe_enable;
timed_dev->get_time = cs40l2x_vibe_get_time;
ret = timed_output_dev_register(timed_dev);
if (ret) {
dev_err(dev, "Failed to register timed output device: %d\n",
ret);
return ret;
}
hrtimer_init(vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
vibe_timer->function = cs40l2x_vibe_timer;
ret = sysfs_create_group(&cs40l2x->timed_dev.dev->kobj,
&cs40l2x_dev_attr_group);
if (ret) {
dev_err(dev, "Failed to create sysfs group: %d\n", ret);
return ret;
}
return 0;
}
#else
/* vibration callback for LED device */
static void cs40l2x_vibe_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct cs40l2x_private *cs40l2x =
container_of(led_cdev, struct cs40l2x_private, led_dev);
switch (brightness) {
case LED_OFF:
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_stop_work);
break;
default:
queue_work(cs40l2x->vibe_workqueue, &cs40l2x->vibe_start_work);
}
}
static int cs40l2x_create_led(struct cs40l2x_private *cs40l2x)
{
int ret;
struct led_classdev *led_dev = &cs40l2x->led_dev;
struct device *dev = cs40l2x->dev;
led_dev->max_brightness = LED_FULL;
led_dev->brightness_set = cs40l2x_vibe_brightness_set;
led_dev->default_trigger = "transient";
led_dev->name = CS40L2X_DEVICE_NAME;
ret = led_classdev_register(dev, led_dev);
if (ret) {
dev_err(dev, "Failed to register LED device: %d\n", ret);
return ret;
}
ret = sysfs_create_group(&cs40l2x->dev->kobj, &cs40l2x_dev_attr_group);
if (ret) {
dev_err(dev, "Failed to create sysfs group: %d\n", ret);
return ret;
}
return 0;
}
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
static int cs40l2x_coeff_init(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
struct cs40l2x_coeff_desc *coeff_desc;
unsigned int reg = CS40L2X_XM_FW_ID;
unsigned int val;
int ret, i;
ret = regmap_read(regmap, CS40L2X_XM_NUM_ALGOS, &val);
if (ret) {
dev_err(dev, "Failed to read number of algorithms\n");
return ret;
}
if (val > CS40L2X_NUM_ALGOS_MAX) {
dev_err(dev, "Invalid number of algorithms\n");
return -EINVAL;
}
cs40l2x->num_algos = val + 1;
for (i = 0; i < cs40l2x->num_algos; i++) {
ret = regmap_read(regmap,
reg + CS40L2X_ALGO_ID_OFFSET,
&cs40l2x->algo_info[i].id);
if (ret) {
dev_err(dev, "Failed to read algo. %d ID\n", i);
return ret;
}
ret = regmap_read(regmap,
reg + CS40L2X_ALGO_REV_OFFSET,
&cs40l2x->algo_info[i].rev);
if (ret) {
dev_err(dev, "Failed to read algo. %d revision\n", i);
return ret;
}
ret = regmap_read(regmap,
reg + CS40L2X_ALGO_XM_BASE_OFFSET,
&cs40l2x->algo_info[i].xm_base);
if (ret) {
dev_err(dev, "Failed to read algo. %d XM_BASE\n", i);
return ret;
}
ret = regmap_read(regmap,
reg + CS40L2X_ALGO_XM_SIZE_OFFSET,
&cs40l2x->algo_info[i].xm_size);
if (ret) {
dev_err(dev, "Failed to read algo. %d XM_SIZE\n", i);
return ret;
}
ret = regmap_read(regmap,
reg + CS40L2X_ALGO_YM_BASE_OFFSET,
&cs40l2x->algo_info[i].ym_base);
if (ret) {
dev_err(dev, "Failed to read algo. %d YM_BASE\n", i);
return ret;
}
ret = regmap_read(regmap,
reg + CS40L2X_ALGO_YM_SIZE_OFFSET,
&cs40l2x->algo_info[i].ym_size);
if (ret) {
dev_err(dev, "Failed to read algo. %d YM_SIZE\n", i);
return ret;
}
list_for_each_entry(coeff_desc,
&cs40l2x->coeff_desc_head, list) {
if (coeff_desc->parent_id != cs40l2x->algo_info[i].id)
continue;
switch (coeff_desc->block_type) {
case CS40L2X_XM_UNPACKED_TYPE:
coeff_desc->reg = CS40L2X_DSP1_XMEM_UNPACK24_0
+ cs40l2x->algo_info[i].xm_base * 4
+ coeff_desc->block_offset * 4;
if (!strncmp(coeff_desc->name, "WAVETABLE",
CS40L2X_COEFF_NAME_LEN_MAX))
cs40l2x->wt_limit_xm =
(cs40l2x->algo_info[i].xm_size
- coeff_desc->block_offset) * 4;
break;
case CS40L2X_YM_UNPACKED_TYPE:
coeff_desc->reg = CS40L2X_DSP1_YMEM_UNPACK24_0
+ cs40l2x->algo_info[i].ym_base * 4
+ coeff_desc->block_offset * 4;
if (!strncmp(coeff_desc->name, "WAVETABLEYM",
CS40L2X_COEFF_NAME_LEN_MAX))
cs40l2x->wt_limit_ym =
(cs40l2x->algo_info[i].ym_size
- coeff_desc->block_offset) * 4;
break;
}
dev_dbg(dev, "Found control %s at 0x%08X\n",
coeff_desc->name, coeff_desc->reg);
}
/* system algo. contains one extra register (num. algos.) */
if (i)
reg += CS40L2X_ALGO_ENTRY_SIZE;
else
reg += (CS40L2X_ALGO_ENTRY_SIZE + 4);
}
ret = regmap_read(regmap, reg, &val);
if (ret) {
dev_err(dev, "Failed to read list terminator\n");
return ret;
}
if (val != CS40L2X_ALGO_LIST_TERM) {
dev_err(dev, "Invalid list terminator: 0x%X\n", val);
return -EINVAL;
}
if (cs40l2x->algo_info[0].id != cs40l2x->fw_desc->id) {
dev_err(dev, "Invalid firmware ID: 0x%06X\n",
cs40l2x->algo_info[0].id);
return -EINVAL;
}
if (cs40l2x->algo_info[0].rev < cs40l2x->fw_desc->min_rev) {
dev_err(dev, "Invalid firmware revision: %d.%d.%d\n",
(cs40l2x->algo_info[0].rev & 0xFF0000) >> 16,
(cs40l2x->algo_info[0].rev & 0xFF00) >> 8,
cs40l2x->algo_info[0].rev & 0xFF);
return -EINVAL;
}
if ((cs40l2x->algo_info[0].rev >= CS40L2X_COND_CLSH_MIN_REV) &&
cs40l2x->pdata.cond_classh)
cs40l2x->cond_class_h_en = true;
if (cs40l2x->algo_info[0].rev >= CS40L2X_PBQ_DUR_MIN_REV)
cs40l2x->comp_dur_min_fw = true;
if (cs40l2x->algo_info[0].rev >= CS40L2X_PWLE_FRQ_MIN_REV)
cs40l2x->ext_freq_min_fw = true;
return 0;
}
static void cs40l2x_coeff_free(struct cs40l2x_private *cs40l2x)
{
struct cs40l2x_coeff_desc *coeff_desc;
while (!list_empty(&cs40l2x->coeff_desc_head)) {
coeff_desc = list_first_entry(&cs40l2x->coeff_desc_head,
struct cs40l2x_coeff_desc, list);
list_del(&coeff_desc->list);
devm_kfree(cs40l2x->dev, coeff_desc);
}
}
static int cs40l2x_hw_err_rls(struct cs40l2x_private *cs40l2x,
unsigned int irq_mask)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
int ret, i;
for (i = 0; i < CS40L2X_NUM_HW_ERRS; i++)
if (cs40l2x_hw_errs[i].irq_mask == irq_mask)
break;
if (i == CS40L2X_NUM_HW_ERRS) {
dev_err(dev, "Unrecognized hardware error\n");
return -EINVAL;
}
if (cs40l2x_hw_errs[i].bst_cycle) {
ret = regmap_update_bits(regmap, CS40L2X_PWR_CTRL2,
CS40L2X_BST_EN_MASK,
CS40L2X_BST_DISABLED << CS40L2X_BST_EN_SHIFT);
if (ret) {
dev_err(dev, "Failed to disable boost converter\n");
return ret;
}
}
ret = regmap_write(regmap, CS40L2X_PROTECT_REL_ERR_IGN, 0);
if (ret) {
dev_err(dev, "Failed to cycle error release (step 1 of 3)\n");
return ret;
}
ret = regmap_write(regmap, CS40L2X_PROTECT_REL_ERR_IGN,
cs40l2x_hw_errs[i].rls_mask);
if (ret) {
dev_err(dev, "Failed to cycle error release (step 2 of 3)\n");
return ret;
}
ret = regmap_write(regmap, CS40L2X_PROTECT_REL_ERR_IGN, 0);
if (ret) {
dev_err(dev, "Failed to cycle error release (step 3 of 3)\n");
return ret;
}
if (cs40l2x_hw_errs[i].bst_cycle) {
ret = regmap_update_bits(regmap, CS40L2X_PWR_CTRL2,
CS40L2X_BST_EN_MASK,
CS40L2X_BST_ENABLED << CS40L2X_BST_EN_SHIFT);
if (ret) {
dev_err(dev, "Failed to re-enable boost converter\n");
return ret;
}
}
dev_info(dev, "Released %s error\n", cs40l2x_hw_errs[i].err_name);
return 0;
}
static int cs40l2x_hw_err_chk(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int val;
int ret, i;
ret = regmap_read(regmap, CS40L2X_IRQ2_STATUS1, &val);
if (ret) {
dev_err(dev, "Failed to read hardware error status\n");
return ret;
}
for (i = 0; i < CS40L2X_NUM_HW_ERRS; i++) {
if (!(val & cs40l2x_hw_errs[i].irq_mask))
continue;
dev_crit(dev, "Encountered %s error\n",
cs40l2x_hw_errs[i].err_name);
ret = regmap_write(regmap, CS40L2X_IRQ2_STATUS1,
cs40l2x_hw_errs[i].irq_mask);
if (ret) {
dev_err(dev, "Failed to acknowledge hardware error\n");
return ret;
}
if (cs40l2x->hw_err_count[i]++ >= CS40L2X_HW_ERR_COUNT_MAX) {
dev_err(dev, "Aborted %s error release\n",
cs40l2x_hw_errs[i].err_name);
continue;
}
ret = cs40l2x_hw_err_rls(cs40l2x, cs40l2x_hw_errs[i].irq_mask);
if (ret)
return ret;
}
return 0;
}
static const struct reg_sequence cs40l2x_irq2_masks[] = {
{CS40L2X_IRQ2_MASK1, 0xFFFFFFFF},
{CS40L2X_IRQ2_MASK2, 0xFFFFFFFF},
{CS40L2X_IRQ2_MASK3, 0xFFFF87FF},
{CS40L2X_IRQ2_MASK4, 0xFEFFFFFF},
};
static const struct reg_sequence cs40l2x_amp_gnd_setup[] = {
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_UNLOCK_CODE1},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_UNLOCK_CODE2},
{CS40L2X_SPK_FORCE_TST_1, CS40L2X_FORCE_SPK_GND},
/* leave test key unlocked to minimize overhead during playback */
};
static const struct reg_sequence cs40l2x_amp_free_setup[] = {
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_UNLOCK_CODE1},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_UNLOCK_CODE2},
{CS40L2X_SPK_FORCE_TST_1, CS40L2X_FORCE_SPK_FREE},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_RELOCK_CODE1},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_RELOCK_CODE2},
};
static int cs40l2x_dsp_pre_config(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int gpio_pol = cs40l2x_dsp_reg(cs40l2x, "GPIO_POL",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
unsigned int spk_auto = cs40l2x_dsp_reg(cs40l2x, "SPK_FORCE_TST_1_AUTO",
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
unsigned int val;
int ret, i;
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL)
return regmap_multi_reg_write(regmap, cs40l2x_amp_free_setup,
ARRAY_SIZE(cs40l2x_amp_free_setup));
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "GPIO_BUTTONDETECT",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
cs40l2x->gpio_mask);
if (ret) {
dev_err(dev, "Failed to enable GPIO detection\n");
return ret;
}
if (gpio_pol) {
ret = regmap_write(regmap, gpio_pol,
cs40l2x->pdata.gpio_indv_pol);
if (ret) {
dev_err(dev, "Failed to configure GPIO polarity\n");
return ret;
}
} else if (cs40l2x->pdata.gpio_indv_pol) {
dev_err(dev, "Active-low GPIO not supported\n");
return -EPERM;
}
if (cs40l2x->pdata.gpio1_mode != CS40L2X_GPIO1_MODE_DEF_ON) {
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "GPIO_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
CS40L2X_GPIO1_DISABLED);
if (ret) {
dev_err(dev, "Failed to pre-configure GPIO1\n");
return ret;
}
}
if (spk_auto) {
ret = regmap_write(regmap, spk_auto,
cs40l2x->pdata.amp_gnd_stby ?
CS40L2X_FORCE_SPK_GND :
CS40L2X_FORCE_SPK_FREE);
if (ret) {
dev_err(dev, "Failed to configure amplifier clamp\n");
return ret;
}
} else if (cs40l2x->event_control != CS40L2X_EVENT_DISABLED) {
cs40l2x->amp_gnd_stby = cs40l2x->pdata.amp_gnd_stby;
}
if (cs40l2x->amp_gnd_stby) {
dev_warn(dev, "Enabling legacy amplifier clamp (no GPIO)\n");
ret = regmap_multi_reg_write(regmap, cs40l2x_amp_gnd_setup,
ARRAY_SIZE(cs40l2x_amp_gnd_setup));
if (ret) {
dev_err(dev, "Failed to ground amplifier outputs\n");
return ret;
}
ret = cs40l2x_wseq_add_seq(cs40l2x, cs40l2x_amp_gnd_setup,
ARRAY_SIZE(cs40l2x_amp_gnd_setup));
if (ret) {
dev_err(dev, "Failed to sequence amplifier outputs\n");
return ret;
}
}
if (cs40l2x->fw_desc->id != CS40L2X_FW_ID_ORIG) {
ret = cs40l2x_wseq_init(cs40l2x);
if (ret) {
dev_err(dev, "Failed to initialize write sequencer\n");
return ret;
}
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "EVENTCONTROL",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
cs40l2x->event_control);
if (ret) {
dev_err(dev, "Failed to configure event controls\n");
return ret;
}
for (i = 0; i < ARRAY_SIZE(cs40l2x_irq2_masks); i++) {
/* unmask hardware error interrupts */
val = cs40l2x_irq2_masks[i].def;
if (cs40l2x_irq2_masks[i].reg == CS40L2X_IRQ2_MASK1)
val &= ~cs40l2x->hw_err_mask;
/* upper half */
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x,
"IRQMASKSEQUENCE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id)
+ i * CS40L2X_IRQMASKSEQ_STRIDE,
(val & CS40L2X_IRQMASKSEQ_MASK1)
<< CS40L2X_IRQMASKSEQ_SHIFTUP);
if (ret) {
dev_err(dev,
"Failed to write IRQMASKSEQ (upper)\n");
return ret;
}
/* lower half */
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x,
"IRQMASKSEQUENCE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id) + 4
+ i * CS40L2X_IRQMASKSEQ_STRIDE,
(val & CS40L2X_IRQMASKSEQ_MASK2)
>> CS40L2X_IRQMASKSEQ_SHIFTDN);
if (ret) {
dev_err(dev,
"Failed to write IRQMASKSEQ (lower)\n");
return ret;
}
}
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x,
"IRQMASKSEQUENCE_VALID",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
1);
if (ret) {
dev_err(dev, "Failed to enable IRQMASKSEQ\n");
return ret;
}
}
return 0;
}
static const struct reg_sequence cs40l2x_dsp_errata[] = {
{CS40L2X_DSP1_XM_ACCEL_PL0_PRI, 0x00000000},
{CS40L2X_DSP1_YM_ACCEL_PL0_PRI, 0x00000000},
{CS40L2X_DSP1_RX1_RATE, 0x00000001},
{CS40L2X_DSP1_RX2_RATE, 0x00000001},
{CS40L2X_DSP1_RX3_RATE, 0x00000001},
{CS40L2X_DSP1_RX4_RATE, 0x00000001},
{CS40L2X_DSP1_RX5_RATE, 0x00000001},
{CS40L2X_DSP1_RX6_RATE, 0x00000001},
{CS40L2X_DSP1_RX7_RATE, 0x00000001},
{CS40L2X_DSP1_RX8_RATE, 0x00000001},
{CS40L2X_DSP1_TX1_RATE, 0x00000001},
{CS40L2X_DSP1_TX2_RATE, 0x00000001},
{CS40L2X_DSP1_TX3_RATE, 0x00000001},
{CS40L2X_DSP1_TX4_RATE, 0x00000001},
{CS40L2X_DSP1_TX5_RATE, 0x00000001},
{CS40L2X_DSP1_TX6_RATE, 0x00000001},
{CS40L2X_DSP1_TX7_RATE, 0x00000001},
{CS40L2X_DSP1_TX8_RATE, 0x00000001},
};
static int cs40l2x_dsp_start(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int dsp_status, dsp_scratch;
int dsp_timeout = CS40L2X_DSP_TIMEOUT_COUNT;
int ret;
ret = regmap_multi_reg_write(regmap, cs40l2x_dsp_errata,
ARRAY_SIZE(cs40l2x_dsp_errata));
if (ret) {
dev_err(dev, "Failed to apply DSP-specific errata\n");
return ret;
}
switch (cs40l2x->revid) {
case CS40L2X_REVID_A0:
ret = regmap_update_bits(regmap, CS40L2X_PWR_CTRL1,
CS40L2X_GLOBAL_EN_MASK,
1 << CS40L2X_GLOBAL_EN_SHIFT);
if (ret) {
dev_err(dev, "Failed to enable device\n");
return ret;
}
break;
default:
ret = regmap_update_bits(regmap, CS40L2X_PWRMGT_CTL,
CS40L2X_MEM_RDY_MASK,
1 << CS40L2X_MEM_RDY_SHIFT);
if (ret) {
dev_err(dev, "Failed to set memory ready flag\n");
return ret;
}
}
ret = regmap_update_bits(regmap, CS40L2X_DSP1_CCM_CORE_CTRL,
CS40L2X_DSP1_RESET_MASK | CS40L2X_DSP1_EN_MASK,
(1 << CS40L2X_DSP1_RESET_SHIFT) |
(1 << CS40L2X_DSP1_EN_SHIFT));
if (ret) {
dev_err(dev, "Failed to start DSP\n");
return ret;
}
while (dsp_timeout > 0) {
usleep_range(10000, 10100);
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "HALO_STATE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
&dsp_status);
if (ret) {
dev_err(dev, "Failed to read DSP status\n");
return ret;
}
if (dsp_status == cs40l2x->fw_desc->halo_state_run)
break;
dsp_timeout--;
}
ret = regmap_read(regmap, CS40L2X_DSP1_SCRATCH1, &dsp_scratch);
if (ret) {
dev_err(dev, "Failed to read DSP scratch contents\n");
return ret;
}
if (dsp_timeout == 0 || dsp_scratch != 0) {
dev_err(dev, "Timed out with DSP status, scratch = %u, %u\n",
dsp_status, dsp_scratch);
return -ETIME;
}
cs40l2x->dsp_reg = cs40l2x_dsp_reg;
return 0;
}
static int cs40l2x_dsp_post_config(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
int ret;
if (cs40l2x->fw_desc->id == CS40L2X_FW_ID_CAL)
return 0;
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "TIMEOUT_MS",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE),
CS40L2X_TIMEOUT_MS_MAX);
if (ret) {
dev_err(dev, "Failed to extend playback timeout\n");
return ret;
}
ret = cs40l2x_wavetable_sync(cs40l2x);
if (ret)
return ret;
ret = cs40l2x_imon_offs_sync(cs40l2x);
if (ret)
return ret;
switch (cs40l2x->fw_desc->id) {
case CS40L2X_FW_ID_ORIG:
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "COMPENSATION_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE),
cs40l2x->comp_enable);
break;
default:
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "COMPENSATION_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE),
(cs40l2x->comp_enable
& cs40l2x->comp_enable_redc)
<< CS40L2X_COMP_EN_REDC_SHIFT |
(cs40l2x->comp_enable
& cs40l2x->comp_enable_f0)
<< CS40L2X_COMP_EN_F0_SHIFT);
}
if (ret) {
dev_err(dev, "Failed to configure click compensation\n");
return ret;
}
if (cs40l2x->pdata.f0_default) {
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "F0_STORED",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id ==
CS40L2X_FW_ID_ORIG ?
CS40L2X_ALGO_ID_F0 :
cs40l2x->fw_desc->id),
cs40l2x->pdata.f0_default);
if (ret) {
dev_err(dev, "Failed to write default f0\n");
return ret;
}
}
if (cs40l2x->pdata.redc_default) {
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "REDC_STORED",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id ==
CS40L2X_FW_ID_ORIG ?
CS40L2X_ALGO_ID_F0 :
cs40l2x->fw_desc->id),
cs40l2x->pdata.redc_default);
if (ret) {
dev_err(dev, "Failed to write default ReDC\n");
return ret;
}
}
if (cs40l2x->pdata.q_default
&& cs40l2x->fw_desc->id != CS40L2X_FW_ID_ORIG) {
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "Q_STORED",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
cs40l2x->pdata.q_default);
if (ret) {
dev_err(dev, "Failed to write default Q\n");
return ret;
}
}
if (cs40l2x->pdata.gpio1_rise_index > 0
&& cs40l2x->pdata.gpio1_rise_index < cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO1) {
ret = cs40l2x_gpio_edge_index_set(cs40l2x,
cs40l2x->pdata.gpio1_rise_index,
CS40L2X_INDEXBUTTONPRESS1, CS40L2X_GPIO_RISE);
if (ret) {
dev_err(dev,
"Failed to write default gpio1_rise_index\n");
return ret;
}
} else if ((cs40l2x->pdata.gpio1_rise_index >= cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO1)
|| (cs40l2x->pdata.gpio1_rise_index > 0
&& !(cs40l2x->gpio_mask
& CS40L2X_GPIO_BTNDETECT_GPIO1))) {
dev_warn(dev, "Ignored default gpio1_rise_index\n");
}
if (cs40l2x->pdata.gpio1_fall_index > 0
&& cs40l2x->pdata.gpio1_fall_index < cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO1) {
ret = cs40l2x_gpio_edge_index_set(cs40l2x,
cs40l2x->pdata.gpio1_fall_index,
CS40L2X_INDEXBUTTONRELEASE1, CS40L2X_GPIO_FALL);
if (ret) {
dev_err(dev,
"Failed to write default gpio1_fall_index\n");
return ret;
}
} else if ((cs40l2x->pdata.gpio1_fall_index >= cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO1)
|| (cs40l2x->pdata.gpio1_fall_index > 0
&& !(cs40l2x->gpio_mask
& CS40L2X_GPIO_BTNDETECT_GPIO1))) {
dev_warn(dev, "Ignored default gpio1_fall_index\n");
}
if (cs40l2x->pdata.gpio1_fall_timeout > 0
&& (cs40l2x->pdata.gpio1_fall_timeout
& CS40L2X_PDATA_MASK)
<= CS40L2X_PR_TIMEOUT_MAX) {
ret = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x,
"PRESS_RELEASE_TIMEOUT",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
cs40l2x->pdata.gpio1_fall_timeout
& CS40L2X_PDATA_MASK);
if (ret) {
dev_err(dev,
"Failed to write default gpio1_fall_timeout\n");
return ret;
}
} else if ((cs40l2x->pdata.gpio1_fall_timeout
& CS40L2X_PDATA_MASK) > CS40L2X_PR_TIMEOUT_MAX) {
dev_warn(dev, "Ignored default gpio1_fall_timeout\n");
}
if (cs40l2x->pdata.gpio2_rise_index > 0
&& cs40l2x->pdata.gpio2_rise_index < cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO2) {
ret = cs40l2x_gpio_edge_index_set(cs40l2x,
cs40l2x->pdata.gpio2_rise_index,
CS40L2X_INDEXBUTTONPRESS2, CS40L2X_GPIO_RISE);
if (ret) {
dev_err(dev,
"Failed to write default gpio2_rise_index\n");
return ret;
}
} else if ((cs40l2x->pdata.gpio2_rise_index >= cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO2)
|| (cs40l2x->pdata.gpio2_rise_index > 0
&& !(cs40l2x->gpio_mask
& CS40L2X_GPIO_BTNDETECT_GPIO2))) {
dev_warn(dev, "Ignored default gpio2_rise_index\n");
}
if (cs40l2x->pdata.gpio2_fall_index > 0
&& cs40l2x->pdata.gpio2_fall_index < cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO2) {
ret = cs40l2x_gpio_edge_index_set(cs40l2x,
cs40l2x->pdata.gpio2_fall_index,
CS40L2X_INDEXBUTTONRELEASE2, CS40L2X_GPIO_FALL);
if (ret) {
dev_err(dev,
"Failed to write default gpio2_fall_index\n");
return ret;
}
} else if ((cs40l2x->pdata.gpio2_fall_index >= cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO2)
|| (cs40l2x->pdata.gpio2_fall_index > 0
&& !(cs40l2x->gpio_mask
& CS40L2X_GPIO_BTNDETECT_GPIO2))) {
dev_warn(dev, "Ignored default gpio2_fall_index\n");
}
if (cs40l2x->pdata.gpio3_rise_index > 0
&& cs40l2x->pdata.gpio3_rise_index < cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO3) {
ret = cs40l2x_gpio_edge_index_set(cs40l2x,
cs40l2x->pdata.gpio3_rise_index,
CS40L2X_INDEXBUTTONPRESS3, CS40L2X_GPIO_RISE);
if (ret) {
dev_err(dev,
"Failed to write default gpio3_rise_index\n");
return ret;
}
} else if ((cs40l2x->pdata.gpio3_rise_index >= cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO3)
|| (cs40l2x->pdata.gpio3_rise_index > 0
&& !(cs40l2x->gpio_mask
& CS40L2X_GPIO_BTNDETECT_GPIO3))) {
dev_warn(dev, "Ignored default gpio3_rise_index\n");
}
if (cs40l2x->pdata.gpio3_fall_index > 0
&& cs40l2x->pdata.gpio3_fall_index < cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO3) {
ret = cs40l2x_gpio_edge_index_set(cs40l2x,
cs40l2x->pdata.gpio3_fall_index,
CS40L2X_INDEXBUTTONRELEASE3, CS40L2X_GPIO_FALL);
if (ret) {
dev_err(dev,
"Failed to write default gpio3_fall_index\n");
return ret;
}
} else if ((cs40l2x->pdata.gpio3_fall_index >= cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO3)
|| (cs40l2x->pdata.gpio3_fall_index > 0
&& !(cs40l2x->gpio_mask
& CS40L2X_GPIO_BTNDETECT_GPIO3))) {
dev_warn(dev, "Ignored default gpio3_fall_index\n");
}
if (cs40l2x->pdata.gpio4_rise_index > 0
&& cs40l2x->pdata.gpio4_rise_index < cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO4) {
ret = cs40l2x_gpio_edge_index_set(cs40l2x,
cs40l2x->pdata.gpio4_rise_index,
CS40L2X_INDEXBUTTONPRESS4, CS40L2X_GPIO_RISE);
if (ret) {
dev_err(dev,
"Failed to write default gpio4_rise_index\n");
return ret;
}
} else if ((cs40l2x->pdata.gpio4_rise_index >= cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO4)
|| (cs40l2x->pdata.gpio4_rise_index > 0
&& !(cs40l2x->gpio_mask
& CS40L2X_GPIO_BTNDETECT_GPIO4))) {
dev_warn(dev, "Ignored default gpio4_rise_index\n");
}
if (cs40l2x->pdata.gpio4_fall_index > 0
&& cs40l2x->pdata.gpio4_fall_index < cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO4) {
ret = cs40l2x_gpio_edge_index_set(cs40l2x,
cs40l2x->pdata.gpio4_fall_index,
CS40L2X_INDEXBUTTONRELEASE4, CS40L2X_GPIO_FALL);
if (ret) {
dev_err(dev,
"Failed to write default gpio4_fall_index\n");
return ret;
}
} else if ((cs40l2x->pdata.gpio4_fall_index >= cs40l2x->num_waves
&& cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO4)
|| (cs40l2x->pdata.gpio4_fall_index > 0
&& !(cs40l2x->gpio_mask
& CS40L2X_GPIO_BTNDETECT_GPIO4))) {
dev_warn(dev, "Ignored default gpio4_fall_index\n");
}
return cs40l2x_hw_err_chk(cs40l2x);
}
static int cs40l2x_raw_write(struct cs40l2x_private *cs40l2x, unsigned int reg,
const void *val, size_t val_len, size_t limit)
{
int ret = 0;
int i;
/* split "val" into smaller writes not to exceed "limit" in length */
for (i = 0; i < val_len; i += limit) {
ret = regmap_raw_write(cs40l2x->regmap, (reg + i), (val + i),
(val_len - i) > limit ? limit : (val_len - i));
if (ret)
break;
}
return ret;
}
int cs40l2x_ack_write(struct cs40l2x_private *cs40l2x, unsigned int reg,
unsigned int write_val, unsigned int reset_val)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int val;
int ret, i;
ret = regmap_write(regmap, reg, write_val);
if (ret) {
/* A NACK is expected when waking from hibernate */
if (reg != CS40L2X_MBOX_POWERCONTROL ||
write_val != CS40L2X_POWERCONTROL_WAKEUP)
dev_err(dev, "Failed to write register 0x%08X: %d\n",
reg, ret);
return ret;
}
for (i = 0; i < CS40L2X_ACK_TIMEOUT_COUNT; i++) {
usleep_range(1000, 1100);
ret = regmap_read(regmap, reg, &val);
if (ret) {
dev_err(dev, "Failed to read register 0x%08X: %d\n",
reg, ret);
return ret;
}
if (val == reset_val)
return 0;
}
dev_err(dev, "Timed out with register 0x%08X = 0x%08X\n", reg, val);
return -ETIME;
}
EXPORT_SYMBOL_GPL(cs40l2x_ack_write);
static void cs40l2x_set_xm(struct cs40l2x_private *cs40l2x, unsigned int pos,
unsigned int reg, unsigned int block_length,
unsigned int size)
{
cs40l2x->wt_open_xm = (cs40l2x->wt_limit_xm - block_length);
/* Set YM available space in case no YM block defined in bin file. */
cs40l2x->wt_open_ym = cs40l2x->wt_limit_ym;
cs40l2x->xm_hdr_strt_pos = pos;
cs40l2x->xm_hdr_strt_reg = reg;
cs40l2x->wt_xm_size = pos + block_length;
cs40l2x->wt_total_size = size;
}
static void cs40l2x_set_ym(struct cs40l2x_private *cs40l2x, unsigned int pos,
unsigned int reg, unsigned int block_length)
{
cs40l2x->wt_open_ym = (cs40l2x->wt_limit_ym - block_length);
cs40l2x->ym_hdr_strt_pos = pos;
cs40l2x->ym_hdr_strt_reg = reg;
cs40l2x->wt_ym_size = ((pos - cs40l2x->wt_xm_size) + block_length);
}
int cs40l2x_coeff_file_parse(struct cs40l2x_private *cs40l2x,
const struct firmware *fw)
{
struct device *dev = cs40l2x->dev;
char wt_date[CS40L2X_WT_FILE_DATE_LEN_MAX];
bool wt_found = false;
unsigned int pos = CS40L2X_WT_FILE_HEADER_SIZE;
unsigned int block_offset, block_type, block_length;
unsigned int algo_id, algo_rev;
unsigned int reg = 0;
unsigned int is_xm;
int ret = -EINVAL;
int i = 0, wt_index = 0;
*wt_date = '\0';
if (memcmp(fw->data, "WMDR", 4)) {
dev_err(dev, "Failed to recognize coefficient file\n");
goto err_rls_fw;
}
if (fw->size % 4) {
dev_err(dev, "Coefficient file is not word-aligned\n");
goto err_rls_fw;
}
while (pos < fw->size) {
block_offset = fw->data[pos]
+ (fw->data[pos + 1] << 8);
pos += CS40L2X_WT_DBLK_OFFSET_SIZE;
block_type = fw->data[pos]
+ (fw->data[pos + 1] << 8);
pos += CS40L2X_WT_DBLK_TYPE_SIZE;
algo_id = fw->data[pos]
+ (fw->data[pos + 1] << 8)
+ (fw->data[pos + 2] << 16)
+ (fw->data[pos + 3] << 24);
pos += CS40L2X_WT_ALGO_ID_SIZE;
algo_rev = fw->data[pos]
+ (fw->data[pos + 1] << 8)
+ (fw->data[pos + 2] << 16)
+ (fw->data[pos + 3] << 24);
pos += CS40L2X_WT_ALGO_REV_SIZE;
/* sample rate is not used here */
pos += CS40L2X_WT_SAMPLE_RATE_SIZE;
block_length = fw->data[pos]
+ (fw->data[pos + 1] << 8)
+ (fw->data[pos + 2] << 16)
+ (fw->data[pos + 3] << 24);
pos += CS40L2X_WT_DBLK_LENGTH_SIZE;
if (block_type != CS40L2X_WMDR_NAME_TYPE
&& block_type != CS40L2X_WMDR_INFO_TYPE) {
for (i = 0; i < cs40l2x->num_algos; i++) {
if (algo_id == cs40l2x->algo_info[i].id)
break;
}
if (i == cs40l2x->num_algos) {
dev_err(dev, "Invalid algo. ID: 0x%06X\n",
algo_id);
ret = -EINVAL;
goto err_rls_fw;
} else {
dev_dbg(dev, "Valid algo ID 0x%x\n", algo_id);
}
if (((algo_rev >> 8) & CS40L2X_ALGO_REV_MASK) !=
(cs40l2x->algo_info[i].rev & CS40L2X_ALGO_REV_MASK)) {
dev_warn(dev, "Algorithm revision mismatch: %d.%d.%d\n",
(algo_rev & 0xFF000000) >> 24,
(algo_rev & 0xFF0000) >> 16,
(algo_rev & 0xFF00) >> 8);
}
switch (algo_id) {
case CS40L2X_ALGO_ID_EXC:
cs40l2x->exc_available = true;
break;
case CS40L2X_ALGO_ID_VIBE:
wt_found = true;
/* intentionally fall through */
}
}
switch (block_type) {
case CS40L2X_WMDR_NAME_TYPE:
case CS40L2X_WMDR_INFO_TYPE:
reg = 0;
if (block_length < CS40L2X_WT_FILE_DATE_LEN_MAX)
break;
if (memcmp(&fw->data[pos], "Date: ", 6))
break;
memcpy(wt_date, &fw->data[pos + 6],
CS40L2X_WT_FILE_DATE_LEN_MAX - 6);
wt_date[CS40L2X_WT_FILE_DATE_LEN_MAX - 6] = '\0';
break;
case CS40L2X_XM_UNPACKED_TYPE:
reg = CS40L2X_DSP1_XMEM_UNPACK24_0
+ block_offset
+ cs40l2x->algo_info[i].xm_base * 4;
if (reg == cs40l2x_dsp_reg(cs40l2x,
"WAVETABLE",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE)) {
if (block_length > cs40l2x->wt_limit_xm) {
dev_err(dev,
"Wvtbl too big: %d bytes XM\n",
block_length / 4 * 3);
ret = -EINVAL;
goto err_rls_fw;
} else {
if (wt_found) {
cs40l2x_set_xm(cs40l2x, pos,
reg, block_length,
fw->size);
memcpy(cs40l2x->pbq_fw_raw_wt,
&fw->data[0],
fw->size);
}
}
}
if (wt_found && cs40l2x->cond_class_h_en) {
ret = cs40l2x_classh_wt_check(cs40l2x,
&fw->data[pos],
block_length, &wt_index);
if (ret)
goto err_rls_fw;
}
break;
case CS40L2X_YM_UNPACKED_TYPE:
reg = CS40L2X_DSP1_YMEM_UNPACK24_0 + block_offset
+ cs40l2x->algo_info[i].ym_base * 4;
if (reg == cs40l2x_dsp_reg(cs40l2x,
"WAVETABLEYM",
CS40L2X_YM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE)) {
if (block_length > cs40l2x->wt_limit_ym) {
dev_err(dev,
"Wvtbl too big: %d bytes YM\n",
block_length / 4 * 3);
ret = -EINVAL;
goto err_rls_fw;
} else {
if (wt_found) {
cs40l2x_set_ym(cs40l2x, pos,
reg, block_length);
}
}
}
if (wt_found && cs40l2x->cond_class_h_en) {
ret = cs40l2x_classh_wt_check(cs40l2x,
&fw->data[pos],
block_length, &wt_index);
if (ret)
goto err_rls_fw;
}
break;
case CS40L2X_XM_PACKED_TYPE:
reg = (CS40L2X_DSP1_XMEM_PACK_0 + block_offset
+ cs40l2x->algo_info[i].xm_base * 3) & ~0x3;
break;
case CS40L2X_YM_PACKED_TYPE:
reg = (CS40L2X_DSP1_YMEM_PACK_0 + block_offset
+ cs40l2x->algo_info[i].ym_base * 3) & ~0x3;
break;
default:
dev_err(dev, "Unexpected block type: 0x%04X\n",
block_type);
ret = -EINVAL;
goto err_rls_fw;
}
if (reg) {
ret = cs40l2x_raw_write(cs40l2x, reg, &fw->data[pos],
block_length, CS40L2X_MAX_WLEN);
if (ret) {
dev_err(dev, "Failed to write coefficients\n");
goto err_rls_fw;
}
} else {
ret = 0;
}
/* blocks are word-aligned */
pos += (block_length + 3) & ~0x00000003;
}
if (wt_found) {
if (!strncmp(cs40l2x->wt_file,
CS40L2X_WT_FILE_NAME_MISSING,
CS40L2X_WT_FILE_NAME_LEN_MAX))
strscpy(cs40l2x->wt_file,
CS40L2X_WT_FILE_NAME_DEFAULT,
CS40L2X_WT_FILE_NAME_LEN_MAX);
if (*wt_date != '\0')
strscpy(cs40l2x->wt_date, wt_date,
CS40L2X_WT_FILE_DATE_LEN_MAX);
else
strscpy(cs40l2x->wt_date,
CS40L2X_WT_FILE_DATE_MISSING,
CS40L2X_WT_FILE_DATE_LEN_MAX);
if (cs40l2x->open_wt_enable) {
ret = cs40l2x_add_wt_slots(cs40l2x, &is_xm);
if (ret) {
dev_err(dev, "Unable to add open slots, open wt disabled\n");
goto err_rls_fw;
}
/* Write updated block with virtual slots */
if (is_xm)
reg = cs40l2x_dsp_reg(cs40l2x,
"WAVETABLE",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE);
else
reg = cs40l2x_dsp_reg(cs40l2x,
"WAVETABLEYM",
CS40L2X_YM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE);
ret = cs40l2x_raw_write(cs40l2x, reg,
&cs40l2x->pbq_updated_fw_raw_wt[0],
cs40l2x->updated_block_size,
CS40L2X_MAX_WLEN);
if (ret) {
dev_err(dev, "Failed to write coefficients\n");
goto err_rls_fw;
}
cs40l2x->virtual_bin = true;
}
}
err_rls_fw:
release_firmware(fw);
return ret;
}
EXPORT_SYMBOL_GPL(cs40l2x_coeff_file_parse);
static void cs40l2x_coeff_file_load(const struct firmware *fw, void *context)
{
struct cs40l2x_private *cs40l2x = (struct cs40l2x_private *)context;
struct device *dev = cs40l2x->dev;
unsigned int num_coeff_files = 0;
unsigned int total_coeff_files = cs40l2x->fw_desc->num_coeff_files;
int ret = 0;
mutex_lock(&cs40l2x->lock);
if (fw)
ret = cs40l2x_coeff_file_parse(cs40l2x, fw);
if (!ret)
num_coeff_files = ++(cs40l2x->num_coeff_files);
if (!cs40l2x->dyn_f0_enable && !cs40l2x->par_bin_found &&
!cs40l2x->clab_bin_found)
total_coeff_files = (cs40l2x->fw_desc->num_coeff_files - 1);
if (num_coeff_files != total_coeff_files)
goto err_mutex;
ret = cs40l2x_dsp_pre_config(cs40l2x);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_start(cs40l2x);
if (ret)
goto err_mutex;
ret = cs40l2x_dsp_post_config(cs40l2x);
if (ret)
goto err_mutex;
#ifdef CONFIG_HAPTICS_CS40L2X_INPUT
ret = cs40l2x_create_input_ff(cs40l2x);
#elif defined CONFIG_ANDROID_TIMED_OUTPUT
ret = cs40l2x_create_timed_output(cs40l2x);
#else
ret = cs40l2x_create_led(cs40l2x);
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
if (ret)
goto err_mutex;
cs40l2x->vibe_init_success = true;
dev_info(cs40l2x->dev, "Firmware revision %d.%d.%d\n",
(cs40l2x->algo_info[0].rev & 0xFF0000) >> 16,
(cs40l2x->algo_info[0].rev & 0xFF00) >> 8,
cs40l2x->algo_info[0].rev & 0xFF);
dev_info(cs40l2x->dev, "Firmware ID 0x%06X\n",
cs40l2x->algo_info[0].id);
dev_info(cs40l2x->dev,
"Max. wavetable size: %d bytes (XM), %d bytes (YM)\n",
cs40l2x->wt_limit_xm / 4 * 3,
cs40l2x->wt_limit_ym / 4 * 3);
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_set_autosuspend_delay(dev, cs40l2x->autosuspend_delay);
pm_runtime_use_autosuspend(dev);
return;
err_mutex:
mutex_unlock(&cs40l2x->lock);
}
static int cs40l2x_algo_parse(struct cs40l2x_private *cs40l2x,
const unsigned char *data)
{
struct cs40l2x_coeff_desc *coeff_desc;
unsigned int pos = 0;
unsigned int algo_id, algo_desc_length, coeff_count;
unsigned int block_offset, block_type, block_length;
unsigned char algo_name_length;
int i;
/* record algorithm ID */
algo_id = *(data + pos)
+ (*(data + pos + 1) << 8)
+ (*(data + pos + 2) << 16)
+ (*(data + pos + 3) << 24);
pos += CS40L2X_ALGO_ID_SIZE;
/* skip past algorithm name */
algo_name_length = *(data + pos);
pos += ((algo_name_length / 4) * 4) + 4;
/* skip past algorithm description */
algo_desc_length = *(data + pos)
+ (*(data + pos + 1) << 8);
pos += ((algo_desc_length / 4) * 4) + 4;
/* record coefficient count */
coeff_count = *(data + pos)
+ (*(data + pos + 1) << 8)
+ (*(data + pos + 2) << 16)
+ (*(data + pos + 3) << 24);
pos += CS40L2X_COEFF_COUNT_SIZE;
for (i = 0; i < coeff_count; i++) {
block_offset = *(data + pos)
+ (*(data + pos + 1) << 8);
pos += CS40L2X_COEFF_OFFSET_SIZE;
block_type = *(data + pos)
+ (*(data + pos + 1) << 8);
pos += CS40L2X_COEFF_TYPE_SIZE;
block_length = *(data + pos)
+ (*(data + pos + 1) << 8)
+ (*(data + pos + 2) << 16)
+ (*(data + pos + 3) << 24);
pos += CS40L2X_COEFF_LENGTH_SIZE;
coeff_desc = devm_kzalloc(cs40l2x->dev,
sizeof(*coeff_desc), GFP_KERNEL);
if (!coeff_desc)
return -ENOMEM;
coeff_desc->parent_id = algo_id;
coeff_desc->block_offset = block_offset;
coeff_desc->block_type = block_type;
memcpy(coeff_desc->name, data + pos + 1, *(data + pos));
coeff_desc->name[*(data + pos)] = '\0';
list_add(&coeff_desc->list, &cs40l2x->coeff_desc_head);
pos += block_length;
}
return 0;
}
static int cs40l2x_firmware_parse(struct cs40l2x_private *cs40l2x,
const struct firmware *fw)
{
struct device *dev = cs40l2x->dev;
unsigned int pos = CS40L2X_FW_FILE_HEADER_SIZE;
unsigned int block_offset, block_type, block_length;
int ret = -EINVAL;
if (memcmp(fw->data, "WMFW", 4)) {
dev_err(dev, "Failed to recognize firmware file\n");
goto err_rls_fw;
}
if (fw->size % 4) {
dev_err(dev, "Firmware file is not word-aligned\n");
goto err_rls_fw;
}
while (pos < fw->size) {
block_offset = fw->data[pos]
+ (fw->data[pos + 1] << 8)
+ (fw->data[pos + 2] << 16);
pos += CS40L2X_FW_DBLK_OFFSET_SIZE;
block_type = fw->data[pos];
pos += CS40L2X_FW_DBLK_TYPE_SIZE;
block_length = fw->data[pos]
+ (fw->data[pos + 1] << 8)
+ (fw->data[pos + 2] << 16)
+ (fw->data[pos + 3] << 24);
pos += CS40L2X_FW_DBLK_LENGTH_SIZE;
switch (block_type) {
case CS40L2X_WMFW_INFO_TYPE:
break;
case CS40L2X_PM_PACKED_TYPE:
ret = cs40l2x_raw_write(cs40l2x,
CS40L2X_DSP1_PMEM_0
+ block_offset * 5,
&fw->data[pos], block_length,
CS40L2X_MAX_WLEN);
if (ret) {
dev_err(dev,
"Failed to write PM_PACKED memory\n");
goto err_rls_fw;
}
break;
case CS40L2X_XM_PACKED_TYPE:
ret = cs40l2x_raw_write(cs40l2x,
CS40L2X_DSP1_XMEM_PACK_0
+ block_offset * 3,
&fw->data[pos], block_length,
CS40L2X_MAX_WLEN);
if (ret) {
dev_err(dev,
"Failed to write XM_PACKED memory\n");
goto err_rls_fw;
}
break;
case CS40L2X_YM_PACKED_TYPE:
ret = cs40l2x_raw_write(cs40l2x,
CS40L2X_DSP1_YMEM_PACK_0
+ block_offset * 3,
&fw->data[pos], block_length,
CS40L2X_MAX_WLEN);
if (ret) {
dev_err(dev,
"Failed to write YM_PACKED memory\n");
goto err_rls_fw;
}
break;
case CS40L2X_ALGO_INFO_TYPE:
ret = cs40l2x_algo_parse(cs40l2x, &fw->data[pos]);
if (ret) {
dev_err(dev,
"Failed to parse algorithm: %d\n", ret);
goto err_rls_fw;
}
break;
default:
dev_err(dev, "Unexpected block type: 0x%02X\n",
block_type);
ret = -EINVAL;
goto err_rls_fw;
}
/* blocks are word-aligned */
pos += (block_length + 3) & ~0x00000003;
}
ret = cs40l2x_coeff_init(cs40l2x);
err_rls_fw:
release_firmware(fw);
return ret;
}
static void cs40l2x_firmware_load(const struct firmware *fw, void *context)
{
struct cs40l2x_private *cs40l2x = (struct cs40l2x_private *)context;
struct device *dev = cs40l2x->dev;
int ret, i;
if (!fw) {
dev_err(dev, "Failed to request firmware file\n");
return;
}
mutex_lock(&cs40l2x->lock);
ret = cs40l2x_firmware_parse(cs40l2x, fw);
if (ret)
goto err_mutex;
for (i = 0; i < cs40l2x->fw_desc->num_coeff_files; i++) {
if ((!cs40l2x->dyn_f0_enable) &&
(!strncmp(cs40l2x->fw_desc->coeff_files[i],
CS40L2X_DYN_F0_FILE_NAME,
CS40L2X_WT_FILE_NAME_LEN_MAX)))
continue;
if (strncmp(cs40l2x->fw_desc->coeff_files[i],
CS40L2X_PAR_CONFIG_FILE_NAME,
CS40L2X_WT_FILE_NAME_LEN_MAX))
cs40l2x->par_bin_found = true;
else if (strncmp(cs40l2x->fw_desc->coeff_files[i],
CS40L2X_CLAB_CONFIG_FILE_NAME,
CS40L2X_WT_FILE_NAME_LEN_MAX))
cs40l2x->clab_bin_found = true;
request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
cs40l2x->fw_desc->coeff_files[i], dev,
GFP_KERNEL, cs40l2x, cs40l2x_coeff_file_load);
}
err_mutex:
mutex_unlock(&cs40l2x->lock);
}
static int cs40l2x_firmware_swap(struct cs40l2x_private *cs40l2x,
unsigned int fw_id)
{
const struct firmware *fw;
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
int ret, i;
if (cs40l2x->vibe_state == CS40L2X_VIBE_STATE_RUNNING)
return -EPERM;
switch (cs40l2x->fw_desc->id) {
case CS40L2X_FW_ID_ORIG:
return -EPERM;
case CS40L2X_FW_ID_B1ROM:
ret = cs40l2x_basic_mode_exit(cs40l2x);
if (ret)
return ret;
/* skip write sequencer if target firmware executes it */
if (fw_id == cs40l2x->fw_id_remap)
break;
for (i = 0; i < cs40l2x->wseq_length; i++) {
ret = regmap_write(regmap,
cs40l2x->wseq_table[i].reg,
cs40l2x->wseq_table[i].val);
if (ret) {
dev_err(dev, "Failed to execute write seq.\n");
return ret;
}
}
break;
case CS40L2X_FW_ID_CAL:
ret = cs40l2x_ack_write(cs40l2x,
cs40l2x_dsp_reg(cs40l2x, "SHUTDOWNREQUEST",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id), 1, 0);
if (ret)
return ret;
break;
default:
ret = cs40l2x_ack_write(cs40l2x,
CS40L2X_MBOX_POWERCONTROL,
CS40L2X_POWERCONTROL_FRC_STDBY,
CS40L2X_POWERCONTROL_NONE);
if (ret)
return ret;
}
cs40l2x->dsp_reg = NULL;
ret = regmap_update_bits(regmap, CS40L2X_DSP1_CCM_CORE_CTRL,
CS40L2X_DSP1_EN_MASK, (0 << CS40L2X_DSP1_EN_SHIFT));
if (ret) {
dev_err(dev, "Failed to stop DSP\n");
return ret;
}
cs40l2x_coeff_free(cs40l2x);
if (fw_id == CS40L2X_FW_ID_CAL) {
cs40l2x->diag_state = CS40L2X_DIAG_STATE_INIT;
cs40l2x->dsp_cache_depth = 0;
if (cs40l2x->open_wt_enable) {
cs40l2x->open_wt_enable = false;
cs40l2x->virtual_bin = false;
cs40l2x->cal_disabled_owt = true;
}
}
cs40l2x->exc_available = false;
cs40l2x->fw_desc = cs40l2x_firmware_match(cs40l2x, fw_id);
if (!cs40l2x->fw_desc)
return -EINVAL;
ret = request_firmware(&fw, cs40l2x->fw_desc->fw_file, dev);
if (ret) {
dev_err(dev, "Failed to request firmware file\n");
return ret;
}
ret = cs40l2x_firmware_parse(cs40l2x, fw);
if (ret)
return ret;
for (i = 0; i < cs40l2x->fw_desc->num_coeff_files; i++) {
/* load alternate wavetable if one has been specified */
if (!strncmp(cs40l2x->fw_desc->coeff_files[i],
CS40L2X_WT_FILE_NAME_DEFAULT,
CS40L2X_WT_FILE_NAME_LEN_MAX)
&& strncmp(cs40l2x->wt_file,
CS40L2X_WT_FILE_NAME_MISSING,
CS40L2X_WT_FILE_NAME_LEN_MAX)) {
ret = request_firmware(&fw, cs40l2x->wt_file, dev);
if (ret)
return ret;
} else {
if ((!cs40l2x->dyn_f0_enable) &&
(!strncmp(cs40l2x->fw_desc->coeff_files[i],
CS40L2X_DYN_F0_FILE_NAME,
CS40L2X_WT_FILE_NAME_LEN_MAX)))
continue;
ret = request_firmware(&fw,
cs40l2x->fw_desc->coeff_files[i], dev);
if (ret)
continue;
}
ret = cs40l2x_coeff_file_parse(cs40l2x, fw);
if (ret)
return ret;
}
ret = cs40l2x_dsp_pre_config(cs40l2x);
if (ret)
return ret;
ret = cs40l2x_dsp_start(cs40l2x);
if (ret)
return ret;
return cs40l2x_dsp_post_config(cs40l2x);
}
static int cs40l2x_wavetable_swap(struct cs40l2x_private *cs40l2x,
const char *wt_file)
{
const struct firmware *fw;
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
int ret1, ret2;
ret1 = cs40l2x_ack_write(cs40l2x,
CS40L2X_MBOX_POWERCONTROL,
CS40L2X_POWERCONTROL_FRC_STDBY,
CS40L2X_POWERCONTROL_NONE);
if (ret1)
return ret1;
ret1 = request_firmware(&fw, wt_file, dev);
if (ret1) {
dev_err(dev, "Failed to request wavetable file\n");
goto err_wakeup;
}
ret1 = cs40l2x_coeff_file_parse(cs40l2x, fw);
if (ret1)
return ret1;
strscpy(cs40l2x->wt_file, wt_file, CS40L2X_WT_FILE_NAME_LEN_MAX);
ret1 = regmap_write(regmap,
cs40l2x_dsp_reg(cs40l2x, "NUMBEROFWAVES",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE),
0);
if (ret1) {
dev_err(dev, "Failed to reset wavetable\n");
return ret1;
}
err_wakeup:
ret2 = cs40l2x_ack_write(cs40l2x,
CS40L2X_MBOX_POWERCONTROL,
CS40L2X_POWERCONTROL_WAKEUP,
CS40L2X_POWERCONTROL_NONE);
if (ret2)
return ret2;
ret2 = cs40l2x_ack_write(cs40l2x, CS40L2X_MBOX_TRIGGERINDEX,
CS40L2X_INDEX_CONT_MIN, CS40L2X_MBOX_TRIGGERRESET);
if (ret2)
return ret2;
return ret1;
}
static int cs40l2x_wavetable_sync(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int cp_trigger_index = cs40l2x->cp_trigger_index;
unsigned int tag, val;
int ret, i;
ret = regmap_read(regmap,
cs40l2x_dsp_reg(cs40l2x, "NUMBEROFWAVES",
CS40L2X_XM_UNPACKED_TYPE,
CS40L2X_ALGO_ID_VIBE),
&cs40l2x->num_waves);
if (ret) {
dev_err(dev, "Failed to count wavetable entries\n");
return ret;
}
if (!cs40l2x->num_waves) {
dev_err(dev, "Wavetable is empty\n");
return -EINVAL;
}
if (!cs40l2x->virtual_bin) {
dev_info(dev, "Loaded %u waveforms from %s, last modified on %s\n",
cs40l2x->num_waves, cs40l2x->wt_file, cs40l2x->wt_date);
if ((cp_trigger_index & CS40L2X_INDEX_MASK) >=
cs40l2x->num_waves
&& cp_trigger_index != CS40L2X_INDEX_QEST
&& cp_trigger_index != CS40L2X_INDEX_PEAK
&& cp_trigger_index != CS40L2X_INDEX_PBQ
&& cp_trigger_index != CS40L2X_INDEX_DIAG)
dev_warn(dev, "Invalid cp_trigger_index\n");
for (i = 0; i < cs40l2x->pbq_comp.nsections; i++) {
tag = cs40l2x->pbq_comp.sections[i].index;
if (tag >= cs40l2x->num_waves)
dev_warn(dev, "Invalid cp_trigger_queue\n");
}
} else {
dev_info(dev, "Loaded %u waveforms from %s, last modified on %s\n",
(cs40l2x->num_waves - CS40L2X_WT_NUM_VIRT_SLOTS),
cs40l2x->wt_file, cs40l2x->wt_date);
}
for (i = 0; i < CS40L2X_NUM_GPIO; i++) {
if (!(cs40l2x->gpio_mask & (1 << i)))
continue;
ret = cs40l2x_gpio_edge_index_get(cs40l2x,
&val, i << 2, CS40L2X_GPIO_RISE);
if (ret)
return ret;
if (!cs40l2x->virtual_bin) {
if (val >= cs40l2x->num_waves)
dev_err(dev, "Invalid gpio%d_rise_index\n",
i + 1);
} else {
if (val >= ((cs40l2x->num_waves +
cs40l2x->num_virtual_waves) -
CS40L2X_WT_NUM_VIRT_SLOTS))
dev_err(dev, "Invalid gpio%d_rise_index\n",
i + 1);
}
ret = cs40l2x_gpio_edge_index_get(cs40l2x,
&val, i << 2, CS40L2X_GPIO_FALL);
if (ret)
return ret;
if (!cs40l2x->virtual_bin) {
if (val >= cs40l2x->num_waves)
dev_err(dev, "Invalid gpio%d_fall_index\n",
i + 1);
} else {
if (val >= ((cs40l2x->num_waves +
cs40l2x->num_virtual_waves) -
CS40L2X_WT_NUM_VIRT_SLOTS))
dev_err(dev, "Invalid gpio%d_fall_index\n",
i + 1);
}
}
return 0;
}
static int cs40l2x_boost_short_test(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int val;
int ret;
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_VCTRL2,
CS40L2X_BST_CTL_SEL_MASK,
CS40L2X_BST_CTL_SEL_CP_VAL
<< CS40L2X_BST_CTL_SEL_SHIFT);
if (ret) {
dev_err(dev, "Failed to change VBST target selection\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L2X_PWR_CTRL1,
CS40L2X_GLOBAL_EN_MASK, 1 << CS40L2X_GLOBAL_EN_SHIFT);
if (ret) {
dev_err(dev, "Failed to enable device\n");
return ret;
}
usleep_range(10000, 10100);
ret = regmap_read(regmap, CS40L2X_IRQ1_STATUS1, &val);
if (ret) {
dev_err(dev, "Failed to read boost converter error status\n");
return ret;
}
if (val & CS40L2X_BST_SHORT_ERR) {
dev_err(dev, "Encountered fatal boost converter short error\n");
return -EIO;
}
ret = regmap_update_bits(regmap, CS40L2X_PWR_CTRL1,
CS40L2X_GLOBAL_EN_MASK, 0 << CS40L2X_GLOBAL_EN_SHIFT);
if (ret) {
dev_err(dev, "Failed to disable device\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_VCTRL2,
CS40L2X_BST_CTL_SEL_MASK,
CS40L2X_BST_CTL_SEL_CLASSH
<< CS40L2X_BST_CTL_SEL_SHIFT);
if (ret) {
dev_err(dev, "Failed to restore VBST target selection\n");
return ret;
}
return cs40l2x_wseq_replace(cs40l2x,
CS40L2X_TEST_LBST, CS40L2X_EXPL_MODE_DIS);
}
static int cs40l2x_boost_config(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int boost_ind = cs40l2x->pdata.boost_ind;
unsigned int boost_cap = cs40l2x->pdata.boost_cap;
unsigned int boost_ipk = cs40l2x->pdata.boost_ipk;
unsigned int boost_ctl = cs40l2x->pdata.boost_ctl;
unsigned int boost_ovp = cs40l2x->pdata.boost_ovp;
unsigned int bst_lbst_val, bst_cbst_range;
unsigned int bst_ipk_scaled, bst_ctl_scaled, bst_ovp_scaled;
int ret;
switch (boost_ind) {
case 1000: /* 1.0 uH */
bst_lbst_val = 0;
break;
case 1200: /* 1.2 uH */
bst_lbst_val = 1;
break;
case 1500: /* 1.5 uH */
bst_lbst_val = 2;
break;
case 2200: /* 2.2 uH */
bst_lbst_val = 3;
break;
default:
dev_err(dev, "Invalid boost inductor value: %d nH\n",
boost_ind);
return -EINVAL;
}
switch (boost_cap) {
case 0 ... 19:
bst_cbst_range = 0;
break;
case 20 ... 50:
bst_cbst_range = 1;
break;
case 51 ... 100:
bst_cbst_range = 2;
break;
case 101 ... 200:
bst_cbst_range = 3;
break;
default: /* 201 uF and greater */
bst_cbst_range = 4;
}
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_COEFF,
CS40L2X_BST_K1_MASK,
cs40l2x_bst_k1_table[bst_lbst_val][bst_cbst_range]
<< CS40L2X_BST_K1_SHIFT);
if (ret) {
dev_err(dev, "Failed to write boost K1 coefficient\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_COEFF,
CS40L2X_BST_K2_MASK,
cs40l2x_bst_k2_table[bst_lbst_val][bst_cbst_range]
<< CS40L2X_BST_K2_SHIFT);
if (ret) {
dev_err(dev, "Failed to write boost K2 coefficient\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_BSTCVRT_COEFF,
(cs40l2x_bst_k2_table[bst_lbst_val][bst_cbst_range]
<< CS40L2X_BST_K2_SHIFT) |
(cs40l2x_bst_k1_table[bst_lbst_val][bst_cbst_range]
<< CS40L2X_BST_K1_SHIFT));
if (ret) {
dev_err(dev, "Failed to sequence boost K1/K2 coefficients\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_SLOPE_LBST,
CS40L2X_BST_SLOPE_MASK,
cs40l2x_bst_slope_table[bst_lbst_val]
<< CS40L2X_BST_SLOPE_SHIFT);
if (ret) {
dev_err(dev, "Failed to write boost slope coefficient\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_SLOPE_LBST,
CS40L2X_BST_LBST_VAL_MASK,
bst_lbst_val << CS40L2X_BST_LBST_VAL_SHIFT);
if (ret) {
dev_err(dev, "Failed to write boost inductor value\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_BSTCVRT_SLOPE_LBST,
(cs40l2x_bst_slope_table[bst_lbst_val]
<< CS40L2X_BST_SLOPE_SHIFT) |
(bst_lbst_val << CS40L2X_BST_LBST_VAL_SHIFT));
if (ret) {
dev_err(dev, "Failed to sequence boost inductor value\n");
return ret;
}
if ((boost_ipk < 1600) || (boost_ipk > 4500)) {
dev_err(dev, "Invalid boost inductor peak current: %d mA\n",
boost_ipk);
return -EINVAL;
}
bst_ipk_scaled = ((boost_ipk - 1600) / 50) + 0x10;
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_PEAK_CUR,
CS40L2X_BST_IPK_MASK,
bst_ipk_scaled << CS40L2X_BST_IPK_SHIFT);
if (ret) {
dev_err(dev, "Failed to write boost inductor peak current\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_BSTCVRT_PEAK_CUR,
bst_ipk_scaled << CS40L2X_BST_IPK_SHIFT);
if (ret) {
dev_err(dev,
"Failed to sequence boost inductor peak current\n");
return ret;
}
if (boost_ctl)
boost_ctl &= CS40L2X_PDATA_MASK;
else
boost_ctl = 11000;
switch (boost_ctl) {
case 0:
bst_ctl_scaled = boost_ctl;
break;
case 2550 ... 11000:
bst_ctl_scaled = ((boost_ctl - 2550) / 50) + 1;
break;
default:
dev_err(dev, "Invalid VBST limit: %d mV\n", boost_ctl);
return -EINVAL;
}
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_VCTRL1,
CS40L2X_BST_CTL_MASK,
bst_ctl_scaled << CS40L2X_BST_CTL_SHIFT);
if (ret) {
dev_err(dev, "Failed to write VBST limit\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_BSTCVRT_VCTRL1,
bst_ctl_scaled << CS40L2X_BST_CTL_SHIFT);
if (ret) {
dev_err(dev, "Failed to sequence VBST limit\n");
return ret;
}
switch (boost_ovp) {
case 0:
break;
case 9000 ... 12875:
bst_ovp_scaled = ((boost_ovp - 9000) / 125) * 2;
ret = regmap_update_bits(regmap, CS40L2X_BSTCVRT_OVERVOLT_CTRL,
CS40L2X_BST_OVP_THLD_MASK,
bst_ovp_scaled << CS40L2X_BST_OVP_THLD_SHIFT);
if (ret) {
dev_err(dev, "Failed to write OVP threshold\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x,
CS40L2X_BSTCVRT_OVERVOLT_CTRL,
(1 << CS40L2X_BST_OVP_EN_SHIFT) |
(bst_ovp_scaled << CS40L2X_BST_OVP_THLD_SHIFT));
if (ret) {
dev_err(dev, "Failed to sequence OVP threshold\n");
return ret;
}
break;
default:
dev_err(dev, "Invalid OVP threshold: %d mV\n", boost_ovp);
return -EINVAL;
}
if (cs40l2x->devid == CS40L2X_DEVID_L20)
return 0;
return cs40l2x_boost_short_test(cs40l2x);
}
static int cs40l2x_asp_config(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int asp_bclk_freq = cs40l2x->pdata.asp_bclk_freq;
unsigned int asp_slot_num = cs40l2x->pdata.asp_slot_num;
unsigned int val;
int ret, i;
if (asp_slot_num > CS40L2X_ASP_RX1_SLOT_MAX) {
dev_err(dev, "Invalid ASP slot number: %d\n", asp_slot_num);
return -EINVAL;
}
for (i = 0; i < CS40L2X_NUM_REFCLKS; i++)
if (cs40l2x_refclks[i].freq == asp_bclk_freq)
break;
if (i == CS40L2X_NUM_REFCLKS) {
dev_err(dev, "Invalid ASP_BCLK frequency: %d Hz\n",
asp_bclk_freq);
return -EINVAL;
}
ret = regmap_write(regmap, CS40L2X_FS_MON_0, cs40l2x_refclks[i].coeff);
if (ret) {
dev_err(dev, "Failed to write ASP coefficients\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_FS_MON_0,
cs40l2x_refclks[i].coeff);
if (ret) {
dev_err(dev, "Failed to sequence ASP coefficients\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L2X_SP_FRAME_RX_SLOT,
CS40L2X_ASP_RX1_SLOT_MASK,
asp_slot_num << CS40L2X_ASP_RX1_SLOT_SHIFT);
if (ret) {
dev_err(dev, "Failed to write ASP slot number\n");
return ret;
}
ret = regmap_read(regmap, CS40L2X_SP_FRAME_RX_SLOT, &val);
if (ret) {
dev_err(dev, "Failed to read ASP slot number\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_SP_FRAME_RX_SLOT, val);
if (ret) {
dev_err(dev, "Failed to sequence ASP slot number\n");
return ret;
}
return 0;
}
static int cs40l2x_brownout_config(struct cs40l2x_private *cs40l2x,
unsigned int br_reg)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
struct cs40l2x_br_desc *br_config;
bool br_enable;
unsigned int br_thld1_scaled = 0;
unsigned int br_thld1, br_thld1_mask, br_thld1_max, br_en_mask, val;
int ret;
switch (br_reg) {
case CS40L2X_VPBR_CFG:
br_enable = cs40l2x->pdata.vpbr_enable;
br_config = &cs40l2x->pdata.vpbr_config;
br_en_mask = CS40L2X_VPBR_EN_MASK;
br_thld1_mask = CS40L2X_VPBR_THLD1_MASK;
br_thld1_max = CS40L2X_VPBR_THLD1_MAX;
br_thld1 = cs40l2x->pdata.vpbr_thld1;
if (!br_thld1)
break;
if ((br_thld1 < 2497) || (br_thld1 > 3874)) {
dev_err(dev, "Invalid VPBR threshold: %u mV\n",
br_thld1);
return -EINVAL;
}
br_thld1_scaled = ((br_thld1 - 2497) * 1000 / 47482) + 0x02;
break;
case CS40L2X_VBBR_CFG:
br_enable = cs40l2x->pdata.vbbr_enable;
br_config = &cs40l2x->pdata.vbbr_config;
br_en_mask = CS40L2X_VBBR_EN_MASK;
br_thld1_mask = CS40L2X_VBBR_THLD1_MASK;
br_thld1_max = CS40L2X_VBBR_THLD1_MAX;
br_thld1 = cs40l2x->pdata.vbbr_thld1;
if (!br_thld1)
break;
if ((br_thld1 < 109) || (br_thld1 > 3445)) {
dev_err(dev, "Invalid VBBR threshold: %u mV\n",
br_thld1);
return -EINVAL;
}
br_thld1_scaled = ((br_thld1 - 109) * 1000 / 54688) + 0x02;
break;
default:
return -EINVAL;
}
br_enable |= br_config->enable;
if (!br_enable)
return 0;
ret = regmap_read(regmap, CS40L2X_PWR_CTRL3, &val);
if (ret) {
dev_err(dev, "Failed to read VPBR/VBBR enable controls\n");
return ret;
}
val |= br_en_mask;
ret = regmap_write(regmap, CS40L2X_PWR_CTRL3, val);
if (ret) {
dev_err(dev, "Failed to write VPBR/VBBR enable controls\n");
return ret;
}
ret = cs40l2x_wseq_replace(cs40l2x, CS40L2X_PWR_CTRL3, val);
if (ret) {
dev_err(dev, "Failed to sequence VPBR/VBBR enable controls\n");
return ret;
}
if (!br_config->present && !br_thld1_scaled)
return 0;
ret = regmap_read(regmap, br_reg, &val);
if (ret) {
dev_err(dev, "Failed to read VPBR/VBBR configuration\n");
return ret;
}
if (br_config->present) {
if (br_config->thld1 > br_thld1_max) {
dev_err(dev, "Invalid VPBR/VBBR threshold: %u\n",
br_config->thld1);
return -EINVAL;
}
val &= ~br_thld1_mask;
val |= (br_config->thld1 << CS40L2X_VxBR_THLD1_SHIFT);
if (br_config->max_att > CS40L2X_VxBR_MAX_ATT_MAX) {
dev_err(dev, "Invalid VPBR/VBBR max. attenuation: %u\n",
br_config->max_att);
return -EINVAL;
}
val &= ~CS40L2X_VxBR_MAX_ATT_MASK;
val |= (br_config->max_att << CS40L2X_VxBR_MAX_ATT_SHIFT);
if (br_config->atk_vol > CS40L2X_VxBR_ATK_VOL_MAX) {
dev_err(dev, "Invalid VPBR/VBBR attack volume: %u\n",
br_config->atk_vol);
return -EINVAL;
}
val &= ~CS40L2X_VxBR_ATK_VOL_MASK;
val |= (br_config->atk_vol << CS40L2X_VxBR_ATK_VOL_SHIFT);
if (br_config->atk_rate > CS40L2X_VxBR_ATK_RATE_MAX) {
dev_err(dev, "Invalid VPBR/VBBR attack rate: %u\n",
br_config->atk_rate);
return -EINVAL;
}
val &= ~CS40L2X_VxBR_ATK_RATE_MASK;
val |= (br_config->atk_rate << CS40L2X_VxBR_ATK_RATE_SHIFT);
if (br_config->wait > CS40L2X_VxBR_WAIT_MAX) {
dev_err(dev, "Invalid VPBR/VBBR wait time: %u\n",
br_config->wait);
return -EINVAL;
}
val &= ~CS40L2X_VxBR_WAIT_MASK;
val |= (br_config->wait << CS40L2X_VxBR_WAIT_SHIFT);
if (br_config->rel_rate > CS40L2X_VxBR_REL_RATE_MAX) {
dev_err(dev, "Invalid VPBR/VBBR release rate: %u\n",
br_config->rel_rate);
return -EINVAL;
}
val &= ~CS40L2X_VxBR_REL_RATE_MASK;
val |= (br_config->rel_rate << CS40L2X_VxBR_REL_RATE_SHIFT);
if (br_config->mute_enable)
val |= CS40L2X_VxBR_MUTE_EN_MASK;
}
if (br_thld1_scaled) {
val &= ~br_thld1_mask;
val |= (br_thld1_scaled << CS40L2X_VxBR_THLD1_SHIFT);
}
ret = regmap_write(regmap, br_reg, val);
if (ret) {
dev_err(dev, "Failed to write VPBR/VBBR configuration\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x, br_reg, val);
if (ret) {
dev_err(dev, "Failed to sequence VPBR/VBBR configuration\n");
return ret;
}
return 0;
}
static int cs40l2x_classh_config(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
unsigned int val_en, val_ctl;
int ret;
ret = regmap_read(regmap, CS40L2X_BSTCVRT_VCTRL2, &val_ctl);
if (ret)
return ret;
ret = regmap_read(regmap, CS40L2X_PWR_CTRL3, &val_en);
if (ret)
return ret;
val_ctl &= ~CS40L2X_BST_CTL_SEL_MASK;
val_ctl |= 1 << CS40L2X_BST_CTL_LIM_EN_SHIFT;
if (!cs40l2x->pdata.cond_classh) {
val_en &= ~(1 << CS40L2X_CLASSH_EN_SHIFT);
} else {
val_en |= 1 << CS40L2X_CLASSH_EN_SHIFT;
val_ctl |= CS40L2X_BST_CTL_SEL_CLASSH;
}
ret = regmap_write(regmap, CS40L2X_BSTCVRT_VCTRL2, val_ctl);
if (ret)
return ret;
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_BSTCVRT_VCTRL2,
val_ctl);
if (ret)
return ret;
ret = regmap_write(regmap, CS40L2X_PWR_CTRL3, val_en);
if (ret)
return ret;
return cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_PWR_CTRL3, val_en);
}
static const struct reg_sequence cs40l2x_mpu_config[] = {
{CS40L2X_DSP1_MPU_LOCK_CONFIG, CS40L2X_MPU_UNLOCK_CODE1},
{CS40L2X_DSP1_MPU_LOCK_CONFIG, CS40L2X_MPU_UNLOCK_CODE2},
{CS40L2X_DSP1_MPU_XM_ACCESS0, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_YM_ACCESS0, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_WNDW_ACCESS0, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_XREG_ACCESS0, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_YREG_ACCESS0, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_WNDW_ACCESS1, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_XREG_ACCESS1, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_YREG_ACCESS1, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_WNDW_ACCESS2, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_XREG_ACCESS2, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_YREG_ACCESS2, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_WNDW_ACCESS3, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_XREG_ACCESS3, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_YREG_ACCESS3, 0xFFFFFFFF},
{CS40L2X_DSP1_MPU_LOCK_CONFIG, 0x00000000}
};
static const struct reg_sequence cs40l2x_pcm_routing[] = {
{CS40L2X_DAC_PCM1_SRC, CS40L2X_DAC_PCM1_SRC_DSP1TX1},
{CS40L2X_DSP1RX1_INPUT, CS40L2X_DSP1_RXn_SRC_ASPRX1},
{CS40L2X_DSP1RX2_INPUT, CS40L2X_DSP1_RXn_SRC_VMON},
{CS40L2X_DSP1RX3_INPUT, CS40L2X_DSP1_RXn_SRC_IMON},
{CS40L2X_DSP1RX4_INPUT, CS40L2X_DSP1_RXn_SRC_VPMON},
};
static int cs40l2x_init(struct cs40l2x_private *cs40l2x)
{
int ret;
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int wksrc_en = CS40L2X_WKSRC_EN_SDA;
unsigned int wksrc_pol = CS40L2X_WKSRC_POL_SDA;
unsigned int wksrc_ctl;
/* REFCLK configuration is handled by revision B1 ROM */
if (cs40l2x->pdata.refclk_gpio2 &&
(cs40l2x->revid < CS40L2X_REVID_B1)) {
ret = regmap_update_bits(regmap, CS40L2X_GPIO_PAD_CONTROL,
CS40L2X_GP2_CTRL_MASK,
CS40L2X_GPx_CTRL_MCLK
<< CS40L2X_GP2_CTRL_SHIFT);
if (ret) {
dev_err(dev, "Failed to select GPIO2 function\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_GPIO_PAD_CONTROL,
((CS40L2X_GPx_CTRL_MCLK
<< CS40L2X_GP2_CTRL_SHIFT)
& CS40L2X_GP2_CTRL_MASK) |
((CS40L2X_GPx_CTRL_GPIO
<< CS40L2X_GP1_CTRL_SHIFT)
& CS40L2X_GP1_CTRL_MASK));
if (ret) {
dev_err(dev,
"Failed to sequence GPIO1/2 configuration\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L2X_PLL_CLK_CTRL,
CS40L2X_PLL_REFCLK_SEL_MASK,
CS40L2X_PLL_REFCLK_SEL_MCLK
<< CS40L2X_PLL_REFCLK_SEL_SHIFT);
if (ret) {
dev_err(dev, "Failed to select clock source\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_PLL_CLK_CTRL,
((1 << CS40L2X_PLL_REFCLK_EN_SHIFT)
& CS40L2X_PLL_REFCLK_EN_MASK) |
((CS40L2X_PLL_REFCLK_SEL_MCLK
<< CS40L2X_PLL_REFCLK_SEL_SHIFT)
& CS40L2X_PLL_REFCLK_SEL_MASK));
if (ret) {
dev_err(dev,
"Failed to sequence PLL configuration\n");
return ret;
}
}
ret = cs40l2x_boost_config(cs40l2x);
if (ret)
return ret;
ret = regmap_multi_reg_write(regmap, cs40l2x_pcm_routing,
ARRAY_SIZE(cs40l2x_pcm_routing));
if (ret) {
dev_err(dev, "Failed to configure PCM channel routing\n");
return ret;
}
ret = cs40l2x_wseq_add_seq(cs40l2x, cs40l2x_pcm_routing,
ARRAY_SIZE(cs40l2x_pcm_routing));
if (ret) {
dev_err(dev, "Failed to sequence PCM channel routing\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_AMP_DIG_VOL_CTRL,
(1 << CS40L2X_AMP_HPF_PCM_EN_SHIFT)
& CS40L2X_AMP_HPF_PCM_EN_MASK);
if (ret) {
dev_err(dev, "Failed to sequence amplifier volume control\n");
return ret;
}
/* revisions A0 and B0 require MPU to be configured manually */
if (cs40l2x->revid < CS40L2X_REVID_B1) {
ret = regmap_multi_reg_write(regmap, cs40l2x_mpu_config,
ARRAY_SIZE(cs40l2x_mpu_config));
if (ret) {
dev_err(dev, "Failed to configure MPU\n");
return ret;
}
}
/* hibernation is supported by revision B1 firmware only */
if (cs40l2x->revid == CS40L2X_REVID_B1) {
/* enables */
if (cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO1)
wksrc_en |= CS40L2X_WKSRC_EN_GPIO1;
if (cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO2)
wksrc_en |= CS40L2X_WKSRC_EN_GPIO2;
if (cs40l2x->gpio_mask & CS40L2X_GPIO_BTNDETECT_GPIO4)
wksrc_en |= CS40L2X_WKSRC_EN_GPIO4;
/* polarities */
if (cs40l2x->pdata.gpio_indv_pol & CS40L2X_GPIO_BTNDETECT_GPIO1)
wksrc_pol |= CS40L2X_WKSRC_POL_GPIO1;
if (cs40l2x->pdata.gpio_indv_pol & CS40L2X_GPIO_BTNDETECT_GPIO2)
wksrc_pol |= CS40L2X_WKSRC_POL_GPIO2;
if (cs40l2x->pdata.gpio_indv_pol & CS40L2X_GPIO_BTNDETECT_GPIO4)
wksrc_pol |= CS40L2X_WKSRC_POL_GPIO4;
wksrc_ctl = ((wksrc_en << CS40L2X_WKSRC_EN_SHIFT)
& CS40L2X_WKSRC_EN_MASK)
| ((wksrc_pol << CS40L2X_WKSRC_POL_SHIFT)
& CS40L2X_WKSRC_POL_MASK);
ret = regmap_write(regmap,
CS40L2X_WAKESRC_CTL, wksrc_ctl);
if (ret) {
dev_err(dev, "Failed to enable wake sources\n");
return ret;
}
ret = cs40l2x_wseq_add_reg(cs40l2x,
CS40L2X_WAKESRC_CTL, wksrc_ctl);
if (ret) {
dev_err(dev, "Failed to sequence wake sources\n");
return ret;
}
}
if (cs40l2x->asp_available) {
ret = cs40l2x_wseq_add_reg(cs40l2x, CS40L2X_PLL_CLK_CTRL,
((1 << CS40L2X_PLL_REFCLK_EN_SHIFT)
& CS40L2X_PLL_REFCLK_EN_MASK) |
((CS40L2X_PLL_REFCLK_SEL_MCLK
<< CS40L2X_PLL_REFCLK_SEL_SHIFT)
& CS40L2X_PLL_REFCLK_SEL_MASK));
if (ret) {
dev_err(dev, "Failed to sequence PLL configuration\n");
return ret;
}
ret = cs40l2x_asp_config(cs40l2x);
if (ret)
return ret;
}
if (cs40l2x->pdata.dcm_disable) {
ret = regmap_write(
regmap, CS40L2X_BSTCVRT_DCM_CTRL, CS40L2X_DCM_DISABLE);
if (ret)
return ret;
ret = cs40l2x_wseq_add_reg(
cs40l2x, CS40L2X_BSTCVRT_DCM_CTRL, CS40L2X_DCM_DISABLE);
if (ret) {
dev_err(dev, "Failed to sequence DCM Control\n");
return ret;
}
}
ret = cs40l2x_classh_config(cs40l2x);
if (ret)
return ret;
ret = cs40l2x_brownout_config(cs40l2x, CS40L2X_VPBR_CFG);
if (ret)
return ret;
return cs40l2x_brownout_config(cs40l2x, CS40L2X_VBBR_CFG);
}
static int cs40l2x_otp_unpack(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
struct cs40l2x_trim trim;
unsigned char row_offset, col_offset;
unsigned int val, otp_map;
unsigned int *otp_mem;
int ret, i;
otp_mem = kmalloc_array(CS40L2X_NUM_OTP_WORDS,
sizeof(*otp_mem), GFP_KERNEL);
if (!otp_mem)
return -ENOMEM;
ret = regmap_read(regmap, CS40L2X_OTPID, &val);
if (ret) {
dev_err(dev, "Failed to read OTP ID\n");
goto err_otp_unpack;
}
/* hard matching against known OTP IDs */
for (i = 0; i < CS40L2X_NUM_OTP_MAPS; i++) {
if (cs40l2x_otp_map[i].id == val) {
otp_map = i;
break;
}
}
/* reject unrecognized IDs, including untrimmed devices (OTP ID = 0) */
if (i == CS40L2X_NUM_OTP_MAPS) {
dev_err(dev, "Unrecognized OTP ID: 0x%01X\n", val);
ret = -ENODEV;
goto err_otp_unpack;
}
dev_dbg(dev, "Found OTP ID: 0x%01X\n", val);
ret = regmap_bulk_read(regmap, CS40L2X_OTP_MEM0, otp_mem,
CS40L2X_NUM_OTP_WORDS);
if (ret) {
dev_err(dev, "Failed to read OTP contents\n");
goto err_otp_unpack;
}
ret = regmap_write(regmap, CS40L2X_TEST_KEY_CTL,
CS40L2X_TEST_KEY_UNLOCK_CODE1);
if (ret) {
dev_err(dev, "Failed to unlock test space (step 1 of 2)\n");
goto err_otp_unpack;
}
ret = regmap_write(regmap, CS40L2X_TEST_KEY_CTL,
CS40L2X_TEST_KEY_UNLOCK_CODE2);
if (ret) {
dev_err(dev, "Failed to unlock test space (step 2 of 2)\n");
goto err_otp_unpack;
}
row_offset = cs40l2x_otp_map[otp_map].row_start;
col_offset = cs40l2x_otp_map[otp_map].col_start;
for (i = 0; i < cs40l2x_otp_map[otp_map].num_trims; i++) {
trim = cs40l2x_otp_map[otp_map].trim_table[i];
if (col_offset + trim.size - 1 > 31) {
/* trim straddles word boundary */
val = (otp_mem[row_offset] &
GENMASK(31, col_offset)) >> col_offset;
val |= (otp_mem[row_offset + 1] &
GENMASK(col_offset + trim.size - 33, 0))
<< (32 - col_offset);
} else {
/* trim does not straddle word boundary */
val = (otp_mem[row_offset] &
GENMASK(col_offset + trim.size - 1,
col_offset)) >> col_offset;
}
/* advance column marker and wrap if necessary */
col_offset += trim.size;
if (col_offset > 31) {
col_offset -= 32;
row_offset++;
}
/* skip blank trims */
if (trim.reg == 0)
continue;
ret = regmap_update_bits(regmap, trim.reg,
GENMASK(trim.shift + trim.size - 1, trim.shift),
val << trim.shift);
if (ret) {
dev_err(dev, "Failed to write trim %d\n", i + 1);
goto err_otp_unpack;
}
dev_dbg(dev, "Trim %d: wrote 0x%X to 0x%08X bits [%d:%d]\n",
i + 1, val, trim.reg,
trim.shift + trim.size - 1, trim.shift);
}
ret = regmap_write(regmap, CS40L2X_TEST_KEY_CTL,
CS40L2X_TEST_KEY_RELOCK_CODE1);
if (ret) {
dev_err(dev, "Failed to lock test space (step 1 of 2)\n");
goto err_otp_unpack;
}
ret = regmap_write(regmap, CS40L2X_TEST_KEY_CTL,
CS40L2X_TEST_KEY_RELOCK_CODE2);
if (ret) {
dev_err(dev, "Failed to lock test space (step 2 of 2)\n");
goto err_otp_unpack;
}
ret = 0;
err_otp_unpack:
kfree(otp_mem);
return ret;
}
static void cs40l2x_handle_br_data(struct device_node *br_node,
struct cs40l2x_br_desc *br_config)
{
int ret;
unsigned int out_val;
if (!br_node)
return;
br_config->present = true;
br_config->enable = of_property_read_bool(br_node,
"cirrus,br-enable");
ret = of_property_read_u32(br_node, "cirrus,br-thld1", &out_val);
if (!ret)
br_config->thld1 = out_val;
ret = of_property_read_u32(br_node, "cirrus,br-max-att", &out_val);
if (!ret)
br_config->max_att = out_val;
ret = of_property_read_u32(br_node, "cirrus,br-atk-vol", &out_val);
if (!ret)
br_config->atk_vol = out_val;
ret = of_property_read_u32(br_node, "cirrus,br-atk-rate", &out_val);
if (!ret)
br_config->atk_rate = out_val;
ret = of_property_read_u32(br_node, "cirrus,br-wait", &out_val);
if (!ret)
br_config->wait = out_val;
ret = of_property_read_u32(br_node, "cirrus,br-rel-rate", &out_val);
if (!ret)
br_config->rel_rate = out_val;
br_config->mute_enable = of_property_read_bool(br_node,
"cirrus,br-mute-enable");
}
static int cs40l2x_handle_of_data(struct i2c_client *i2c_client,
struct cs40l2x_platform_data *pdata)
{
struct device_node *vpbr_node, *vbbr_node;
struct device_node *np = i2c_client->dev.of_node;
struct device *dev = &i2c_client->dev;
int ret;
unsigned int out_val;
if (!np)
return 0;
ret = of_property_read_u32(np, "cirrus,boost-ind-nanohenry", &out_val);
if (ret) {
dev_err(dev, "Boost inductor value not specified\n");
return -EINVAL;
}
pdata->boost_ind = out_val;
ret = of_property_read_u32(np, "cirrus,boost-cap-microfarad", &out_val);
if (ret) {
dev_err(dev, "Boost capacitance not specified\n");
return -EINVAL;
}
pdata->boost_cap = out_val;
ret = of_property_read_u32(np, "cirrus,boost-ipk-milliamp", &out_val);
if (ret) {
dev_err(dev, "Boost inductor peak current not specified\n");
return -EINVAL;
}
pdata->boost_ipk = out_val;
ret = of_property_read_u32(np, "cirrus,boost-ctl-millivolt", &out_val);
if (!ret)
pdata->boost_ctl = out_val | CS40L2X_PDATA_PRESENT;
ret = of_property_read_u32(np, "cirrus,boost-clab-millivolt", &out_val);
if (!ret)
pdata->boost_clab = out_val | CS40L2X_PDATA_PRESENT;
ret = of_property_read_u32(np, "cirrus,boost-ovp-millivolt", &out_val);
if (!ret)
pdata->boost_ovp = out_val;
pdata->refclk_gpio2 = of_property_read_bool(np, "cirrus,refclk-gpio2");
ret = of_property_read_u32(np, "cirrus,f0-default", &out_val);
if (!ret)
pdata->f0_default = out_val;
ret = of_property_read_u32(np, "cirrus,f0-min", &out_val);
if (!ret)
pdata->f0_min = out_val;
ret = of_property_read_u32(np, "cirrus,f0-max", &out_val);
if (!ret)
pdata->f0_max = out_val;
ret = of_property_read_u32(np, "cirrus,redc-default", &out_val);
if (!ret)
pdata->redc_default = out_val;
ret = of_property_read_u32(np, "cirrus,redc-min", &out_val);
if (!ret)
pdata->redc_min = out_val;
ret = of_property_read_u32(np, "cirrus,redc-max", &out_val);
if (!ret)
pdata->redc_max = out_val;
ret = of_property_read_u32(np, "cirrus,q-default", &out_val);
if (!ret)
pdata->q_default = out_val;
ret = of_property_read_u32(np, "cirrus,q-min", &out_val);
if (!ret)
pdata->q_min = out_val;
ret = of_property_read_u32(np, "cirrus,q-max", &out_val);
if (!ret)
pdata->q_max = out_val;
pdata->redc_comp_disable = of_property_read_bool(np,
"cirrus,redc-comp-disable");
pdata->comp_disable = of_property_read_bool(np, "cirrus,comp-disable");
pdata->dyn_f0_disable = of_property_read_bool(np,
"cirrus,dyn-f0-disable");
pdata->open_wt_disable = of_property_read_bool(np,
"cirrus,open-wt-disable");
ret = of_property_read_u32(np, "cirrus,gpio1-rise-index", &out_val);
if (!ret)
pdata->gpio1_rise_index = out_val;
ret = of_property_read_u32(np, "cirrus,gpio1-fall-index", &out_val);
if (!ret)
pdata->gpio1_fall_index = out_val;
ret = of_property_read_u32(np, "cirrus,gpio1-fall-timeout", &out_val);
if (!ret)
pdata->gpio1_fall_timeout = out_val | CS40L2X_PDATA_PRESENT;
ret = of_property_read_u32(np, "cirrus,gpio1-mode", &out_val);
if (!ret) {
if (out_val > CS40L2X_GPIO1_MODE_MAX)
dev_warn(dev, "Ignored default gpio1_mode\n");
else
pdata->gpio1_mode = out_val;
}
ret = of_property_read_u32(np, "cirrus,gpio2-rise-index", &out_val);
if (!ret)
pdata->gpio2_rise_index = out_val;
ret = of_property_read_u32(np, "cirrus,gpio2-fall-index", &out_val);
if (!ret)
pdata->gpio2_fall_index = out_val;
ret = of_property_read_u32(np, "cirrus,gpio3-rise-index", &out_val);
if (!ret)
pdata->gpio3_rise_index = out_val;
ret = of_property_read_u32(np, "cirrus,gpio3-fall-index", &out_val);
if (!ret)
pdata->gpio3_fall_index = out_val;
ret = of_property_read_u32(np, "cirrus,gpio4-rise-index", &out_val);
if (!ret)
pdata->gpio4_rise_index = out_val;
ret = of_property_read_u32(np, "cirrus,gpio4-fall-index", &out_val);
if (!ret)
pdata->gpio4_fall_index = out_val;
ret = of_property_read_u32(np, "cirrus,gpio-indv-enable", &out_val);
if (!ret) {
if (out_val > (CS40L2X_GPIO_BTNDETECT_GPIO1
| CS40L2X_GPIO_BTNDETECT_GPIO2
| CS40L2X_GPIO_BTNDETECT_GPIO3
| CS40L2X_GPIO_BTNDETECT_GPIO4))
dev_warn(dev, "Ignored default gpio_indv_enable\n");
else
pdata->gpio_indv_enable = out_val;
}
ret = of_property_read_u32(np, "cirrus,gpio-indv-pol", &out_val);
if (!ret) {
if (out_val > (CS40L2X_GPIO_BTNDETECT_GPIO1
| CS40L2X_GPIO_BTNDETECT_GPIO2
| CS40L2X_GPIO_BTNDETECT_GPIO3
| CS40L2X_GPIO_BTNDETECT_GPIO4))
dev_warn(dev, "Ignored default gpio_indv_pol\n");
else
pdata->gpio_indv_pol = out_val;
}
pdata->hiber_enable = of_property_read_bool(np, "cirrus,hiber-enable");
ret = of_property_read_u32(np, "cirrus,asp-bclk-freq-hz", &out_val);
if (!ret)
pdata->asp_bclk_freq = out_val;
ret = of_property_read_u32(np, "cirrus,asp-slot-num", &out_val);
if (!ret)
pdata->asp_slot_num = out_val;
ret = of_property_read_u32(np, "cirrus,asp-timeout", &out_val);
if (!ret) {
if (out_val > CS40L2X_ASP_TIMEOUT_MAX)
dev_warn(dev, "Ignored default ASP timeout\n");
else
pdata->asp_timeout = out_val;
}
pdata->vpbr_enable = of_property_read_bool(np, "cirrus,vpbr-enable");
pdata->vbbr_enable = of_property_read_bool(np, "cirrus,vbbr-enable");
ret = of_property_read_u32(np, "cirrus,vpbr-thld1-millivolt", &out_val);
if (!ret)
pdata->vpbr_thld1 = out_val;
ret = of_property_read_u32(np, "cirrus,vbbr-thld1-millivolt", &out_val);
if (!ret)
pdata->vbbr_thld1 = out_val;
vpbr_node = of_get_child_by_name(np, "cirrus,vpbr-config");
cs40l2x_handle_br_data(vpbr_node, &pdata->vpbr_config);
of_node_put(vpbr_node);
vbbr_node = of_get_child_by_name(np, "cirrus,vbbr-config");
cs40l2x_handle_br_data(vbbr_node, &pdata->vbbr_config);
of_node_put(vbbr_node);
ret = of_property_read_u32(np, "cirrus,fw-id-remap", &out_val);
if (!ret)
pdata->fw_id_remap = out_val;
pdata->amp_gnd_stby = of_property_read_bool(np, "cirrus,amp-gnd-stby");
pdata->auto_recovery = of_property_read_bool(np,
"cirrus,auto-recovery");
pdata->dcm_disable = of_property_read_bool(np, "cirrus,dcm-disable");
pdata->cond_classh = of_property_read_bool(np, "cirrus,cond-classh");
return 0;
}
static const struct reg_sequence cs40l2x_basic_mode_revert[] = {
{CS40L2X_PWR_CTRL1, 0x00000000},
{CS40L2X_PWR_CTRL2, 0x00003321},
{CS40L2X_LRCK_PAD_CONTROL, 0x00000007},
{CS40L2X_SDIN_PAD_CONTROL, 0x00000007},
{CS40L2X_AMP_DIG_VOL_CTRL, 0x00008000},
{CS40L2X_IRQ2_MASK1, 0xFFFFFFFF},
{CS40L2X_IRQ2_MASK2, 0xFFFFFFFF},
};
static int cs40l2x_basic_mode_exit(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int val, hb_init;
int ret, i;
for (i = 0; i < CS40L2X_BASIC_TIMEOUT_COUNT; i++) {
ret = regmap_read(regmap, CS40L2X_BASIC_AMP_STATUS, &val);
if (ret) {
dev_err(dev, "Failed to read basic-mode boot status\n");
return ret;
}
if (val & CS40L2X_BASIC_BOOT_DONE)
break;
usleep_range(5000, 5100);
}
if (i == CS40L2X_BASIC_TIMEOUT_COUNT) {
dev_err(dev, "Timed out waiting for basic-mode boot\n");
return -ETIME;
}
ret = regmap_read(regmap, CS40L2X_BASIC_HALO_HEARTBEAT, &hb_init);
if (ret) {
dev_err(dev, "Failed to read basic-mode heartbeat\n");
return ret;
}
for (i = 0; i < CS40L2X_BASIC_TIMEOUT_COUNT; i++) {
usleep_range(5000, 5100);
ret = regmap_read(regmap, CS40L2X_BASIC_HALO_HEARTBEAT, &val);
if (ret) {
dev_err(dev, "Failed to read basic-mode heartbeat\n");
return ret;
}
if (val > hb_init)
break;
}
if (i == CS40L2X_BASIC_TIMEOUT_COUNT) {
dev_err(dev, "Timed out waiting for basic-mode heartbeat\n");
return -ETIME;
}
ret = cs40l2x_ack_write(cs40l2x, CS40L2X_BASIC_SHUTDOWNREQUEST, 1, 0);
if (ret)
return ret;
ret = regmap_read(regmap, CS40L2X_BASIC_STATEMACHINE, &val);
if (ret) {
dev_err(dev, "Failed to read basic-mode state\n");
return ret;
}
if (val != CS40L2X_BASIC_SHUTDOWN) {
dev_err(dev, "Unexpected basic-mode state: 0x%02X\n", val);
return -EBUSY;
}
ret = regmap_read(regmap, CS40L2X_BASIC_AMP_STATUS, &val);
if (ret) {
dev_err(dev, "Failed to read basic-mode error status\n");
return ret;
}
if (val & CS40L2X_BASIC_OTP_ERROR) {
dev_err(dev, "Encountered basic-mode OTP error\n");
return -EIO;
}
if (val & CS40L2X_BASIC_AMP_ERROR) {
ret = cs40l2x_hw_err_rls(cs40l2x, CS40L2X_AMP_ERR);
if (ret)
return ret;
}
if (val & CS40L2X_BASIC_TEMP_RISE_WARN) {
ret = cs40l2x_hw_err_rls(cs40l2x, CS40L2X_TEMP_RISE_WARN);
if (ret)
return ret;
}
if (val & CS40L2X_BASIC_TEMP_ERROR) {
ret = cs40l2x_hw_err_rls(cs40l2x, CS40L2X_TEMP_ERR);
if (ret)
return ret;
}
ret = regmap_multi_reg_write(regmap, cs40l2x_basic_mode_revert,
ARRAY_SIZE(cs40l2x_basic_mode_revert));
if (ret) {
dev_err(dev, "Failed to revert basic-mode fields\n");
return ret;
}
return 0;
}
static const struct reg_sequence cs40l2x_rev_a0_errata[] = {
{CS40L2X_OTP_TRIM_30, 0x9091A1C8},
{CS40L2X_PLL_LOOP_PARAM, 0x000C1837},
{CS40L2X_PLL_MISC_CTRL, 0x03008E0E},
{CS40L2X_BSTCVRT_DCM_CTRL, 0x00000051},
{CS40L2X_CTRL_ASYNC1, 0x00000004},
{CS40L2X_IRQ1_DB3, 0x00000000},
{CS40L2X_IRQ2_DB3, 0x00000000},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_UNLOCK_CODE1},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_UNLOCK_CODE2},
{CS40L2X_SPKMON_RESYNC, 0x00000000},
{CS40L2X_TEMP_RESYNC, 0x00000000},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_RELOCK_CODE1},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_RELOCK_CODE2},
{CS40L2X_VPVBST_FS_SEL, 0x00000000},
};
static const struct reg_sequence cs40l2x_rev_b0_errata[] = {
{CS40L2X_PLL_LOOP_PARAM, 0x000C1837},
{CS40L2X_PLL_MISC_CTRL, 0x03008E0E},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_UNLOCK_CODE1},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_UNLOCK_CODE2},
{CS40L2X_TEST_LBST, CS40L2X_EXPL_MODE_EN},
{CS40L2X_OTP_TRIM_12, 0x002F0065},
{CS40L2X_OTP_TRIM_13, 0x00002B4F},
{CS40L2X_SPKMON_RESYNC, 0x00000000},
{CS40L2X_TEMP_RESYNC, 0x00000000},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_RELOCK_CODE1},
{CS40L2X_TEST_KEY_CTL, CS40L2X_TEST_KEY_RELOCK_CODE2},
{CS40L2X_VPVBST_FS_SEL, 0x00000000},
};
static int cs40l2x_part_num_resolve(struct cs40l2x_private *cs40l2x)
{
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int val, devid, revid;
unsigned int part_num_index, fw_id;
int otp_timeout = CS40L2X_OTP_TIMEOUT_COUNT;
int ret;
while (otp_timeout > 0) {
usleep_range(10000, 10100);
ret = regmap_read(regmap, CS40L2X_IRQ1_STATUS4, &val);
if (ret) {
dev_err(dev, "Failed to read OTP boot status\n");
return ret;
}
if (val & CS40L2X_OTP_BOOT_DONE)
break;
otp_timeout--;
}
if (otp_timeout == 0) {
dev_err(dev, "Timed out waiting for OTP boot\n");
return -ETIME;
}
ret = regmap_read(regmap, CS40L2X_IRQ1_STATUS3, &val);
if (ret) {
dev_err(dev, "Failed to read OTP error status\n");
return ret;
}
if (val & CS40L2X_OTP_BOOT_ERR) {
dev_err(dev, "Encountered fatal OTP error\n");
return -EIO;
}
ret = regmap_read(regmap, CS40L2X_DEVID, &devid);
if (ret) {
dev_err(dev, "Failed to read device ID\n");
return ret;
}
ret = regmap_read(regmap, CS40L2X_REVID, &revid);
if (ret) {
dev_err(dev, "Failed to read revision ID\n");
return ret;
}
switch (devid) {
case CS40L2X_DEVID_L20:
part_num_index = 0;
fw_id = CS40L2X_FW_ID_ORIG;
if (revid != CS40L2X_REVID_A0)
goto err_revid;
ret = regmap_register_patch(regmap, cs40l2x_rev_a0_errata,
ARRAY_SIZE(cs40l2x_rev_a0_errata));
if (ret) {
dev_err(dev, "Failed to apply revision %02X errata\n",
revid);
return ret;
}
ret = cs40l2x_otp_unpack(cs40l2x);
if (ret)
return ret;
break;
case CS40L2X_DEVID_L25:
part_num_index = 1;
fw_id = CS40L2X_FW_ID_ORIG;
if (revid != CS40L2X_REVID_B0)
goto err_revid;
ret = regmap_register_patch(regmap, cs40l2x_rev_b0_errata,
ARRAY_SIZE(cs40l2x_rev_b0_errata));
if (ret) {
dev_err(dev, "Failed to apply revision %02X errata\n",
revid);
return ret;
}
ret = cs40l2x_wseq_add_seq(cs40l2x, cs40l2x_rev_b0_errata,
ARRAY_SIZE(cs40l2x_rev_b0_errata));
if (ret) {
dev_err(dev,
"Failed to sequence revision %02X errata\n",
revid);
return ret;
}
break;
case CS40L2X_DEVID_L25A:
case CS40L2X_DEVID_L25B:
part_num_index = devid - CS40L2X_DEVID_L25A + 2;
fw_id = cs40l2x->fw_id_remap;
if (revid < CS40L2X_REVID_B1)
goto err_revid;
ret = cs40l2x_basic_mode_exit(cs40l2x);
if (ret)
return ret;
ret = regmap_register_patch(regmap, cs40l2x_rev_b0_errata,
ARRAY_SIZE(cs40l2x_rev_b0_errata));
if (ret) {
dev_err(dev, "Failed to apply revision %02X errata\n",
revid);
return ret;
}
ret = cs40l2x_wseq_add_seq(cs40l2x, cs40l2x_rev_b0_errata,
ARRAY_SIZE(cs40l2x_rev_b0_errata));
if (ret) {
dev_err(dev,
"Failed to sequence revision %02X errata\n",
revid);
return ret;
}
break;
default:
dev_err(dev, "Unrecognized device ID: 0x%06X\n", devid);
return -ENODEV;
}
cs40l2x->fw_desc = cs40l2x_firmware_match(cs40l2x, fw_id);
if (!cs40l2x->fw_desc)
return -EINVAL;
dev_info(dev, "Cirrus Logic %s revision %02X\n",
cs40l2x_part_nums[part_num_index], revid);
cs40l2x->devid = devid;
cs40l2x->revid = revid;
return 0;
err_revid:
dev_err(dev, "Unexpected revision ID for %s: %02X\n",
cs40l2x_part_nums[part_num_index], revid);
return -ENODEV;
}
static irqreturn_t cs40l2x_irq(int irq, void *data)
{
struct cs40l2x_private *cs40l2x = (struct cs40l2x_private *)data;
struct regmap *regmap = cs40l2x->regmap;
struct device *dev = cs40l2x->dev;
unsigned int asp_timeout = cs40l2x->pdata.asp_timeout;
unsigned int event_reg, val;
int event_count = 0;
int ret, i;
irqreturn_t ret_irq = IRQ_NONE;
pm_runtime_get_sync(cs40l2x->dev);
mutex_lock(&cs40l2x->lock);
ret = regmap_read(regmap, CS40L2X_DSP1_SCRATCH1, &val);
if (ret) {
dev_err(dev, "Failed to read DSP scratch contents\n");
goto err_exit;
}
if (val) {
dev_err(dev, "Fatal runtime error with DSP scratch = %u\n",
val);
ret = cs40l2x_reset_recovery(cs40l2x);
if (!ret)
ret_irq = IRQ_HANDLED;
goto err_exit;
}
for (i = 0; i < ARRAY_SIZE(cs40l2x_event_regs); i++) {
/* skip disabled event notifiers */
if (!(cs40l2x->event_control & cs40l2x_event_masks[i]))
continue;
event_reg = cs40l2x_dsp_reg(cs40l2x, cs40l2x_event_regs[i],
CS40L2X_XM_UNPACKED_TYPE, cs40l2x->fw_desc->id);
if (!event_reg)
goto err_exit;
ret = regmap_read(regmap, event_reg, &val);
if (ret) {
dev_err(dev, "Failed to read %s\n",
cs40l2x_event_regs[i]);
goto err_exit;
}
/* any event handling goes here */
switch (val) {
case CS40L2X_EVENT_CTRL_NONE:
continue;
case CS40L2X_EVENT_CTRL_HARDWARE:
ret = cs40l2x_hw_err_chk(cs40l2x);
if (ret)
goto err_exit;
break;
case CS40L2X_EVENT_CTRL_TRIG_STOP:
queue_work(cs40l2x->vibe_workqueue,
&cs40l2x->vibe_pbq_work);
fallthrough;
case CS40L2X_EVENT_CTRL_GPIO_STOP:
if (asp_timeout > 0)
hrtimer_start(&cs40l2x->asp_timer,
ktime_set(asp_timeout / 1000,
(asp_timeout % 1000)
* 1000000),
HRTIMER_MODE_REL);
else
queue_work(cs40l2x->vibe_workqueue,
&cs40l2x->vibe_mode_work);
if (val == CS40L2X_EVENT_CTRL_GPIO_STOP)
cs40l2x_set_gpio_event(cs40l2x, true);
complete(&cs40l2x->hap_done);
break;
case CS40L2X_EVENT_CTRL_TRIG_START:
case CS40L2X_EVENT_CTRL_GPIO_START:
if (completion_done(&cs40l2x->hap_done))
reinit_completion(&cs40l2x->hap_done);
break;
case CS40L2X_EVENT_CTRL_GPIO1_FALL
... CS40L2X_EVENT_CTRL_GPIO4_RISE:
case CS40L2X_EVENT_CTRL_READY:
case CS40L2X_EVENT_CTRL_TRIG_SUSP
... CS40L2X_EVENT_CTRL_TRIG_RESM:
dev_dbg(dev, "Found notifier %d in %s\n",
val, cs40l2x_event_regs[i]);
break;
default:
dev_err(dev, "Unrecognized notifier %d in %s\n",
val, cs40l2x_event_regs[i]);
goto err_exit;
}
ret = regmap_write(regmap, event_reg, CS40L2X_EVENT_CTRL_NONE);
if (ret) {
dev_err(dev, "Failed to acknowledge %s\n",
cs40l2x_event_regs[i]);
goto err_exit;
}
/*
* polling for acknowledgment as with other mailbox registers
* is unnecessary in this case and adds latency, so only send
* the wake-up command to complete the notification sequence
*/
ret = regmap_write(regmap, CS40L2X_MBOX_POWERCONTROL,
CS40L2X_POWERCONTROL_WAKEUP);
if (ret) {
dev_err(dev, "Failed to free /ALERT output\n");
goto err_exit;
}
event_count++;
}
if (event_count > 0)
ret_irq = IRQ_HANDLED;
err_exit:
mutex_unlock(&cs40l2x->lock);
pm_runtime_mark_last_busy(cs40l2x->dev);
pm_runtime_put_autosuspend(cs40l2x->dev);
return ret_irq;
}
static struct regmap_config cs40l2x_regmap = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.reg_format_endian = REGMAP_ENDIAN_BIG,
.val_format_endian = REGMAP_ENDIAN_BIG,
.max_register = CS40L2X_LASTREG,
.precious_reg = cs40l2x_precious_reg,
.readable_reg = cs40l2x_readable_reg,
.cache_type = REGCACHE_NONE,
};
static const struct mfd_cell cs40l2x_devs[] = {
{
.name = "cs40l2x-codec",
.of_compatible = "cs40l2x-codec",
},
};
static int cs40l2x_i2c_probe(struct i2c_client *i2c_client,
const struct i2c_device_id *id)
{
int ret, i;
struct cs40l2x_private *cs40l2x;
struct device *dev = &i2c_client->dev;
struct cs40l2x_platform_data *pdata = dev_get_platdata(dev);
cs40l2x = devm_kzalloc(dev, sizeof(struct cs40l2x_private), GFP_KERNEL);
if (!cs40l2x)
return -ENOMEM;
cs40l2x->dev = dev;
dev_set_drvdata(dev, cs40l2x);
i2c_set_clientdata(i2c_client, cs40l2x);
mutex_init(&cs40l2x->lock);
hrtimer_init(&cs40l2x->pbq_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
cs40l2x->pbq_timer.function = cs40l2x_pbq_timer;
hrtimer_init(&cs40l2x->asp_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
cs40l2x->asp_timer.function = cs40l2x_asp_timer;
cs40l2x->vibe_workqueue =
alloc_ordered_workqueue("vibe_workqueue", WQ_HIGHPRI);
if (!cs40l2x->vibe_workqueue) {
dev_err(dev, "Failed to allocate workqueue\n");
return -ENOMEM;
}
INIT_WORK(&cs40l2x->vibe_start_work, cs40l2x_vibe_start_worker);
INIT_WORK(&cs40l2x->vibe_pbq_work, cs40l2x_vibe_pbq_worker);
INIT_WORK(&cs40l2x->vibe_stop_work, cs40l2x_vibe_stop_worker);
INIT_WORK(&cs40l2x->vibe_mode_work, cs40l2x_vibe_mode_worker);
ret = device_init_wakeup(cs40l2x->dev, true);
if (ret) {
dev_err(dev, "Failed to initialize wakeup source\n");
return ret;
}
INIT_LIST_HEAD(&cs40l2x->coeff_desc_head);
INIT_LIST_HEAD(&cs40l2x->virtual_waveform_head);
cs40l2x->regmap = devm_regmap_init_i2c(i2c_client, &cs40l2x_regmap);
if (IS_ERR(cs40l2x->regmap)) {
ret = PTR_ERR(cs40l2x->regmap);
dev_err(dev, "Failed to allocate register map: %d\n", ret);
return ret;
}
for (i = 0; i < ARRAY_SIZE(cs40l2x_supplies); i++)
cs40l2x->supplies[i].supply = cs40l2x_supplies[i];
cs40l2x->num_supplies = ARRAY_SIZE(cs40l2x_supplies);
ret = devm_regulator_bulk_get(dev, cs40l2x->num_supplies,
cs40l2x->supplies);
if (ret) {
dev_err(dev, "Failed to request core supplies: %d\n", ret);
return ret;
}
if (pdata) {
cs40l2x->pdata = *pdata;
} else {
pdata = devm_kzalloc(dev, sizeof(struct cs40l2x_platform_data),
GFP_KERNEL);
if (!pdata)
return -ENOMEM;
if (i2c_client->dev.of_node) {
ret = cs40l2x_handle_of_data(i2c_client, pdata);
if (ret)
return ret;
}
cs40l2x->pdata = *pdata;
}
cs40l2x->cp_trailer_index = CS40L2X_INDEX_IDLE;
cs40l2x->vpp_measured = -1;
cs40l2x->ipp_measured = -1;
cs40l2x->comp_enable = !pdata->comp_disable;
cs40l2x->comp_enable_redc = !pdata->redc_comp_disable;
cs40l2x->comp_enable_f0 = true;
cs40l2x->dyn_f0_enable = !pdata->dyn_f0_disable;
cs40l2x->open_wt_enable = !pdata->open_wt_disable;
cs40l2x->autosuspend_delay = CS40L2X_AUTOSUSPEND_DELAY_MS;
strscpy(cs40l2x->wt_file,
CS40L2X_WT_FILE_NAME_MISSING,
CS40L2X_WT_FILE_NAME_LEN_MAX);
strscpy(cs40l2x->wt_date,
CS40L2X_WT_FILE_DATE_MISSING,
CS40L2X_WT_FILE_DATE_LEN_MAX);
/* Virtual wavetable slots mem init */
cs40l2x->updated_offsets = devm_kzalloc(dev,
(CS40L2X_OWT_CALC_SIZE * sizeof(unsigned int)),
GFP_KERNEL);
if (!cs40l2x->updated_offsets)
return -ENOMEM;
cs40l2x->pbq_updated_fw_raw_wt = devm_kzalloc(dev,
CS40L2X_WT_MAX_BIN_SIZE, GFP_KERNEL);
if (!cs40l2x->pbq_updated_fw_raw_wt)
return -ENOMEM;
cs40l2x->wvfrm_lengths = devm_kzalloc(dev,
(CS40L2X_OWT_CALC_SIZE * sizeof(unsigned int)),
GFP_KERNEL);
if (!cs40l2x->wvfrm_lengths)
return -ENOMEM;
cs40l2x->pbq_fw_raw_wt = devm_kzalloc(dev,
CS40L2X_WT_MAX_BIN_SIZE, GFP_KERNEL);
if (!cs40l2x->pbq_fw_raw_wt)
return -ENOMEM;
cs40l2x->ovwr_wav = devm_kzalloc(dev,
sizeof(struct cs40l2x_ovwr_waveform), GFP_KERNEL);
if (!cs40l2x->ovwr_wav)
return -ENOMEM;
cs40l2x->virtual_stored = true;
cs40l2x->safe_save_state = true;
switch (pdata->fw_id_remap) {
case CS40L2X_FW_ID_ORIG:
case CS40L2X_FW_ID_B1ROM:
case CS40L2X_FW_ID_CAL:
dev_err(dev, "Unexpected firmware ID: 0x%06X\n",
pdata->fw_id_remap);
return -EINVAL;
case 0:
cs40l2x->fw_id_remap = CS40L2X_FW_ID_REMAP;
break;
default:
cs40l2x->fw_id_remap = pdata->fw_id_remap;
}
for (i = 0; i < CS40L2X_NUM_HW_ERRS; i++)
cs40l2x->hw_err_mask |= cs40l2x_hw_errs[i].irq_mask;
ret = regulator_bulk_enable(cs40l2x->num_supplies, cs40l2x->supplies);
if (ret) {
dev_err(dev, "Failed to enable core supplies: %d\n", ret);
return ret;
}
cs40l2x->reset_gpio = devm_gpiod_get_optional(dev, "reset",
GPIOD_OUT_LOW);
if (IS_ERR(cs40l2x->reset_gpio))
return PTR_ERR(cs40l2x->reset_gpio);
/* satisfy reset pulse width specification (with margin) */
usleep_range(2000, 2100);
gpiod_set_value_cansleep(cs40l2x->reset_gpio, 1);
/* satisfy control port delay specification (with margin) */
usleep_range(1000, 1100);
ret = cs40l2x_part_num_resolve(cs40l2x);
if (ret)
goto err;
cs40l2x->asp_available = (cs40l2x->devid == CS40L2X_DEVID_L25A) &&
pdata->asp_bclk_freq;
init_completion(&cs40l2x->hap_done);
if (cs40l2x->fw_desc->id != CS40L2X_FW_ID_ORIG && i2c_client->irq) {
ret = devm_request_threaded_irq(dev, i2c_client->irq,
NULL, cs40l2x_irq,
IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW,
i2c_client->name, cs40l2x);
if (ret) {
dev_err(dev, "Failed to request IRQ: %d\n", ret);
goto err;
}
cs40l2x->event_control = CS40L2X_EVENT_HARDWARE_ENABLED
| CS40L2X_EVENT_END_ENABLED;
} else {
cs40l2x->event_control = CS40L2X_EVENT_DISABLED;
}
if (!pdata->gpio_indv_enable
|| cs40l2x->fw_desc->id == CS40L2X_FW_ID_ORIG) {
cs40l2x->gpio_mask = CS40L2X_GPIO_BTNDETECT_GPIO1;
if (cs40l2x->devid == CS40L2X_DEVID_L25B)
cs40l2x->gpio_mask |= (CS40L2X_GPIO_BTNDETECT_GPIO2
| CS40L2X_GPIO_BTNDETECT_GPIO3
| CS40L2X_GPIO_BTNDETECT_GPIO4);
} else {
cs40l2x->gpio_mask = pdata->gpio_indv_enable;
if (cs40l2x->devid == CS40L2X_DEVID_L25A)
cs40l2x->gpio_mask &= ~CS40L2X_GPIO_BTNDETECT_GPIO2;
}
ret = cs40l2x_init(cs40l2x);
if (ret)
goto err;
request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
cs40l2x->fw_desc->fw_file, dev, GFP_KERNEL, cs40l2x,
cs40l2x_firmware_load);
ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cs40l2x_devs,
ARRAY_SIZE(cs40l2x_devs), NULL, 0, NULL);
if (ret) {
dev_err(dev, "Cannot register codec component\n");
goto err;
}
return 0;
err:
gpiod_set_value_cansleep(cs40l2x->reset_gpio, 0);
regulator_bulk_disable(cs40l2x->num_supplies, cs40l2x->supplies);
return ret;
}
static void cs40l2x_i2c_remove(struct i2c_client *i2c_client)
{
struct cs40l2x_private *cs40l2x = i2c_get_clientdata(i2c_client);
pm_runtime_disable(&i2c_client->dev);
/* manually free irq ahead of destroying workqueue */
if (cs40l2x->event_control != CS40L2X_EVENT_DISABLED)
devm_free_irq(&i2c_client->dev, i2c_client->irq, cs40l2x);
if (cs40l2x->vibe_init_success) {
#ifdef CONFIG_HAPTICS_CS40L2X_INPUT
input_unregister_device(cs40l2x->input);
sysfs_remove_group(&cs40l2x->input->dev.kobj,
&cs40l2x_dev_attr_group);
#elif defined CONFIG_ANDROID_TIMED_OUTPUT
hrtimer_cancel(&cs40l2x->vibe_timer);
timed_output_dev_unregister(&cs40l2x->timed_dev);
sysfs_remove_group(&cs40l2x->timed_dev.dev->kobj,
&cs40l2x_dev_attr_group);
#else
led_classdev_unregister(&cs40l2x->led_dev);
sysfs_remove_group(&cs40l2x->dev->kobj,
&cs40l2x_dev_attr_group);
#endif /* CONFIG_ANDROID_TIMED_OUTPUT */
}
hrtimer_cancel(&cs40l2x->pbq_timer);
hrtimer_cancel(&cs40l2x->asp_timer);
if (cs40l2x->vibe_workqueue) {
cancel_work_sync(&cs40l2x->vibe_start_work);
cancel_work_sync(&cs40l2x->vibe_pbq_work);
cancel_work_sync(&cs40l2x->vibe_stop_work);
cancel_work_sync(&cs40l2x->vibe_mode_work);
destroy_workqueue(cs40l2x->vibe_workqueue);
}
device_init_wakeup(cs40l2x->dev, false);
gpiod_set_value_cansleep(cs40l2x->reset_gpio, 0);
regulator_bulk_disable(cs40l2x->num_supplies, cs40l2x->supplies);
mutex_destroy(&cs40l2x->lock);
}
static int __maybe_unused cs40l2x_suspend(struct device *dev)
{
struct cs40l2x_private *cs40l2x = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&cs40l2x->lock);
if (cs40l2x->pdata.gpio1_mode == CS40L2X_GPIO1_MODE_AUTO
&& cs40l2x->fw_desc->id != CS40L2X_FW_ID_CAL) {
ret = regmap_write(cs40l2x->regmap,
cs40l2x_dsp_reg(cs40l2x, "GPIO_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
CS40L2X_GPIO1_ENABLED);
if (ret) {
dev_err(dev, "Failed to enable GPIO1 upon suspend: %d\n",
ret);
goto err_mutex;
}
}
if (cs40l2x->pdata.hiber_enable
&& cs40l2x->fw_desc->id != CS40L2X_FW_ID_CAL
&& cs40l2x->fw_desc->id != CS40L2X_FW_ID_ORIG) {
ret = cs40l2x_hiber_cmd_send(cs40l2x,
CS40L2X_POWERCONTROL_HIBERNATE);
if (ret)
dev_err(dev, "Failed to hibernate upon suspend: %d\n",
ret);
}
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static int __maybe_unused cs40l2x_resume(struct device *dev)
{
struct cs40l2x_private *cs40l2x = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&cs40l2x->lock);
if (cs40l2x->pdata.hiber_enable
&& cs40l2x->fw_desc->id != CS40L2X_FW_ID_CAL
&& cs40l2x->fw_desc->id != CS40L2X_FW_ID_ORIG) {
ret = cs40l2x_hiber_cmd_send(cs40l2x,
CS40L2X_POWERCONTROL_WAKEUP);
if (ret) {
dev_err(dev, "Failed to wake up upon resume: %d\n",
ret);
goto err_mutex;
}
}
if (cs40l2x->pdata.gpio1_mode == CS40L2X_GPIO1_MODE_AUTO
&& cs40l2x->fw_desc->id != CS40L2X_FW_ID_CAL) {
ret = regmap_write(cs40l2x->regmap,
cs40l2x_dsp_reg(cs40l2x, "GPIO_ENABLE",
CS40L2X_XM_UNPACKED_TYPE,
cs40l2x->fw_desc->id),
CS40L2X_GPIO1_DISABLED);
if (ret)
dev_err(dev, "Failed to disable GPIO1 upon resume; %d\n",
ret);
}
err_mutex:
mutex_unlock(&cs40l2x->lock);
return ret;
}
static int cs40l2x_sys_suspend(struct device *dev)
{
struct cs40l2x_private *cs40l2x = dev_get_drvdata(dev);
struct i2c_client *i2c_client = to_i2c_client(dev);
dev_dbg(cs40l2x->dev, "System suspend, disabling IRQ\n");
disable_irq(i2c_client->irq);
return 0;
}
static int cs40l2x_sys_suspend_noirq(struct device *dev)
{
struct cs40l2x_private *cs40l2x = dev_get_drvdata(dev);
struct i2c_client *i2c_client = to_i2c_client(dev);
dev_dbg(cs40l2x->dev, "Late system suspend, reenabling IRQ\n");
enable_irq(i2c_client->irq);
return 0;
}
static int cs40l2x_sys_resume_noirq(struct device *dev)
{
struct cs40l2x_private *cs40l2x = dev_get_drvdata(dev);
struct i2c_client *i2c_client = to_i2c_client(dev);
dev_dbg(cs40l2x->dev, "Early system resume, disabling IRQ\n");
disable_irq(i2c_client->irq);
return 0;
}
static int cs40l2x_sys_resume(struct device *dev)
{
struct cs40l2x_private *cs40l2x = dev_get_drvdata(dev);
struct i2c_client *i2c_client = to_i2c_client(dev);
dev_dbg(cs40l2x->dev, "System resume, reenabling IRQ\n");
enable_irq(i2c_client->irq);
return 0;
}
static const struct dev_pm_ops cs40l2x_pm_ops = {
SET_RUNTIME_PM_OPS(cs40l2x_suspend, cs40l2x_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(cs40l2x_sys_suspend, cs40l2x_sys_resume)
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs40l2x_sys_suspend_noirq,
cs40l2x_sys_resume_noirq)
};
static const struct of_device_id cs40l2x_of_match[] = {
{ .compatible = "cirrus,cs40l20" },
{ .compatible = "cirrus,cs40l25" },
{ .compatible = "cirrus,cs40l25a" },
{ .compatible = "cirrus,cs40l25b" },
{ }
};
MODULE_DEVICE_TABLE(of, cs40l2x_of_match);
static const struct i2c_device_id cs40l2x_id[] = {
{ "cs40l20", 0 },
{ "cs40l25", 1 },
{ "cs40l25a", 2 },
{ "cs40l25b", 3 },
{ }
};
MODULE_DEVICE_TABLE(i2c, cs40l2x_id);
static struct i2c_driver cs40l2x_i2c_driver = {
.driver = {
.name = "cs40l2x",
.of_match_table = cs40l2x_of_match,
.pm = &cs40l2x_pm_ops,
},
.id_table = cs40l2x_id,
.probe = cs40l2x_i2c_probe,
.remove = cs40l2x_i2c_remove,
};
module_i2c_driver(cs40l2x_i2c_driver);
MODULE_DESCRIPTION("CS40L20/CS40L25/CS40L25A/CS40L25B Haptics Driver");
MODULE_AUTHOR("Jeff LaBundy, Cirrus Logic Inc, <[email protected]>");
MODULE_LICENSE("GPL");