blob: e0d30d7fcf60fb84e8771c57cb2cf9b1e0de424c [file] [log] [blame]
/*
* cs40l20.c -- CS40L20 Haptics Driver
*
* Copyright 2017 Cirrus Logic, Inc.
*
* Author: Jeff LaBundy <jeff.labundy@cirrus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/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/leds.h>
#include <linux/sysfs.h>
#include <linux/firmware.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/platform_data/cs40l20.h>
#include "cs40l20.h"
struct cs40l20_private {
struct device *dev;
struct regmap *regmap;
struct regulator_bulk_data supplies[2];
unsigned int num_supplies;
unsigned int devid;
unsigned int revid;
struct work_struct vibe_start_work;
struct work_struct vibe_stop_work;
struct workqueue_struct *vibe_workqueue;
struct mutex lock;
unsigned int cp_trigger_index;
unsigned int cp_trailer_index;
unsigned int num_waves;
unsigned int vibegen_id;
unsigned int vibegen_rev;
unsigned int wt_limit_xm;
unsigned int wt_limit_ym;
bool vibe_init_success;
struct gpio_desc *reset_gpio;
struct cs40l20_platform_data pdata;
struct list_head coeff_desc_head;
unsigned char diag_state;
unsigned int f0_measured;
unsigned int redc_measured;
struct led_classdev led_dev;
unsigned int dig_scale;
};
static const char *const cs40l20_supplies[] = {
"VA",
"VP",
};
static const char * const cs40l20_part_nums[] = {
"CS40L20",
"CS40L25",
"CS40L25A",
"CS40L25B",
};
static struct cs40l20_private *cs40l20_get_private(struct device *dev)
{
return dev_get_drvdata(dev);
}
static ssize_t cs40l20_cp_trigger_index_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", cs40l20->cp_trigger_index);
}
static ssize_t cs40l20_cp_trigger_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
if ((index & CS40L20_INDEX_MASK) >= cs40l20->num_waves
&& index != CS40L20_INDEX_DIAG)
return -EINVAL;
cs40l20->cp_trigger_index = index;
return count;
}
static unsigned int cs40l20_dsp_reg(struct cs40l20_private *cs40l20,
const char *coeff_name, const unsigned char block_type)
{
struct cs40l20_coeff_desc *coeff_desc;
list_for_each_entry(coeff_desc, &cs40l20->coeff_desc_head, list) {
if (strcmp(coeff_desc->name, coeff_name))
continue;
if (coeff_desc->block_type != block_type)
continue;
return coeff_desc->reg;
}
/* return an identifiable register that is known to be read-only */
return CS40L20_DEVID;
}
static ssize_t cs40l20_gpio1_rise_index_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
mutex_lock(&cs40l20->lock);
ret = regmap_read(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "INDEXBUTTONPRESS",
CS40L20_XM_UNPACKED_TYPE), &val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to read index\n");
return ret;
}
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t cs40l20_gpio1_rise_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
if (index > (cs40l20->num_waves - 1))
return -EINVAL;
mutex_lock(&cs40l20->lock);
ret = regmap_write(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "INDEXBUTTONPRESS",
CS40L20_XM_UNPACKED_TYPE), index);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to write index\n");
return ret;
}
return count;
}
static ssize_t cs40l20_gpio1_fall_index_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
mutex_lock(&cs40l20->lock);
ret = regmap_read(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "INDEXBUTTONRELEASE",
CS40L20_XM_UNPACKED_TYPE), &val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to read index\n");
return ret;
}
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t cs40l20_gpio1_fall_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int index;
ret = kstrtou32(buf, 10, &index);
if (ret)
return -EINVAL;
if (index > (cs40l20->num_waves - 1))
return -EINVAL;
mutex_lock(&cs40l20->lock);
ret = regmap_write(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "INDEXBUTTONRELEASE",
CS40L20_XM_UNPACKED_TYPE), index);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to write index\n");
return ret;
}
return count;
}
static ssize_t cs40l20_gpio1_fall_timeout_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
mutex_lock(&cs40l20->lock);
ret = regmap_read(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "PRESS_RELEASE_TIMEOUT",
CS40L20_XM_UNPACKED_TYPE), &val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to read GPIO1 falling-edge timeout\n");
return ret;
}
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t cs40l20_gpio1_fall_timeout_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
mutex_lock(&cs40l20->lock);
ret = regmap_write(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "PRESS_RELEASE_TIMEOUT",
CS40L20_XM_UNPACKED_TYPE), val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to write GPIO1 falling-edge timeout\n");
return ret;
}
return count;
}
static ssize_t cs40l20_standby_timeout_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
mutex_lock(&cs40l20->lock);
ret = regmap_read(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "EVENT_TIMEOUT",
CS40L20_XM_UNPACKED_TYPE), &val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to read standby timeout\n");
return ret;
}
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t cs40l20_standby_timeout_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
mutex_lock(&cs40l20->lock);
ret = regmap_write(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "EVENT_TIMEOUT",
CS40L20_XM_UNPACKED_TYPE), val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to write standby timeout\n");
return ret;
}
return count;
}
static ssize_t cs40l20_f0_measured_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
unsigned char diag_state;
unsigned int f0_measured;
mutex_lock(&cs40l20->lock);
diag_state = cs40l20->diag_state;
f0_measured = cs40l20->f0_measured;
mutex_unlock(&cs40l20->lock);
if (diag_state != CS40L20_DIAG_STATE_DONE)
return -ENODATA;
return snprintf(buf, PAGE_SIZE, "%d\n", f0_measured);
}
static ssize_t cs40l20_f0_stored_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
mutex_lock(&cs40l20->lock);
ret = regmap_read(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "F0_STORED",
CS40L20_XM_UNPACKED_TYPE), &val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to read stored f0\n");
return ret;
}
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t cs40l20_f0_stored_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
mutex_lock(&cs40l20->lock);
ret = regmap_write(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "F0_STORED",
CS40L20_XM_UNPACKED_TYPE), val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to store f0\n");
return ret;
}
return count;
}
static ssize_t cs40l20_redc_measured_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
unsigned char diag_state;
unsigned int redc_measured;
mutex_lock(&cs40l20->lock);
diag_state = cs40l20->diag_state;
redc_measured = cs40l20->redc_measured;
mutex_unlock(&cs40l20->lock);
if (diag_state != CS40L20_DIAG_STATE_DONE)
return -ENODATA;
return snprintf(buf, PAGE_SIZE, "%d\n", redc_measured);
}
static ssize_t cs40l20_redc_stored_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
mutex_lock(&cs40l20->lock);
ret = regmap_read(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "REDC_STORED",
CS40L20_XM_UNPACKED_TYPE), &val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to read stored ReDC\n");
return ret;
}
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t cs40l20_redc_stored_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
mutex_lock(&cs40l20->lock);
ret = regmap_write(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "REDC_STORED",
CS40L20_XM_UNPACKED_TYPE), val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to store ReDC\n");
return ret;
}
return count;
}
static ssize_t cs40l20_comp_enable_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
mutex_lock(&cs40l20->lock);
ret = regmap_read(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "COMPENSATION_ENABLE",
CS40L20_XM_UNPACKED_TYPE), &val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to read compensation state\n");
return ret;
}
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t cs40l20_comp_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
ret = kstrtou32(buf, 10, &val);
if (ret)
return -EINVAL;
mutex_lock(&cs40l20->lock);
ret = regmap_write(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "COMPENSATION_ENABLE",
CS40L20_XM_UNPACKED_TYPE), val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to write compensation state\n");
return ret;
}
return count;
}
static ssize_t cs40l20_dig_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", cs40l20->dig_scale);
}
static ssize_t cs40l20_dig_scale_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int dig_scale;
ret = kstrtou32(buf, 10, &dig_scale);
if (ret)
return -EINVAL;
if (dig_scale > CS40L20_DIG_SCALE_MAX)
return -EINVAL;
mutex_lock(&cs40l20->lock);
ret = regmap_update_bits(cs40l20->regmap, CS40L20_AMP_DIG_VOL_CTRL,
CS40L20_AMP_VOL_PCM_MASK,
((0x800 - dig_scale) & 0x7FF)
<< CS40L20_AMP_VOL_PCM_SHIFT);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to store digital scale\n");
return ret;
}
cs40l20->dig_scale = dig_scale;
return count;
}
static ssize_t cs40l20_heartbeat_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
int ret;
unsigned int val;
mutex_lock(&cs40l20->lock);
ret = regmap_read(cs40l20->regmap,
cs40l20_dsp_reg(cs40l20, "HALO_HEARTBEAT",
CS40L20_XM_UNPACKED_TYPE), &val);
mutex_unlock(&cs40l20->lock);
if (ret) {
pr_err("Failed to read heartbeat\n");
return ret;
}
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t cs40l20_num_waves_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs40l20_private *cs40l20 = cs40l20_get_private(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", cs40l20->num_waves);
}
static DEVICE_ATTR(cp_trigger_index, 0660, cs40l20_cp_trigger_index_show,
cs40l20_cp_trigger_index_store);
static DEVICE_ATTR(gpio1_rise_index, 0660, cs40l20_gpio1_rise_index_show,
cs40l20_gpio1_rise_index_store);
static DEVICE_ATTR(gpio1_fall_index, 0660, cs40l20_gpio1_fall_index_show,
cs40l20_gpio1_fall_index_store);
static DEVICE_ATTR(gpio1_fall_timeout, 0660, cs40l20_gpio1_fall_timeout_show,
cs40l20_gpio1_fall_timeout_store);
static DEVICE_ATTR(standby_timeout, 0660, cs40l20_standby_timeout_show,
cs40l20_standby_timeout_store);
static DEVICE_ATTR(f0_measured, 0660, cs40l20_f0_measured_show, NULL);
static DEVICE_ATTR(f0_stored, 0660, cs40l20_f0_stored_show,
cs40l20_f0_stored_store);
static DEVICE_ATTR(redc_measured, 0660, cs40l20_redc_measured_show, NULL);
static DEVICE_ATTR(redc_stored, 0660, cs40l20_redc_stored_show,
cs40l20_redc_stored_store);
static DEVICE_ATTR(comp_enable, 0660, cs40l20_comp_enable_show,
cs40l20_comp_enable_store);
static DEVICE_ATTR(dig_scale, 0660, cs40l20_dig_scale_show,
cs40l20_dig_scale_store);
static DEVICE_ATTR(heartbeat, 0660, cs40l20_heartbeat_show, NULL);
static DEVICE_ATTR(num_waves, 0660, cs40l20_num_waves_show, NULL);
static struct attribute *cs40l20_dev_attrs[] = {
&dev_attr_cp_trigger_index.attr,
&dev_attr_gpio1_rise_index.attr,
&dev_attr_gpio1_fall_index.attr,
&dev_attr_gpio1_fall_timeout.attr,
&dev_attr_standby_timeout.attr,
&dev_attr_f0_measured.attr,
&dev_attr_f0_stored.attr,
&dev_attr_redc_measured.attr,
&dev_attr_redc_stored.attr,
&dev_attr_comp_enable.attr,
&dev_attr_dig_scale.attr,
&dev_attr_heartbeat.attr,
&dev_attr_num_waves.attr,
NULL,
};
static struct attribute_group cs40l20_dev_attr_group = {
.attrs = cs40l20_dev_attrs,
};
static int cs40l20_diag_capture(struct cs40l20_private *cs40l20)
{
struct regmap *regmap = cs40l20->regmap;
int ret;
/* this function expects to be called from a locked worker function */
if (!mutex_is_locked(&cs40l20->lock))
return -EACCES;
if (cs40l20->diag_state != CS40L20_DIAG_STATE_RUN)
return -ENODATA;
ret = regmap_read(regmap, cs40l20_dsp_reg(cs40l20, "F0",
CS40L20_XM_UNPACKED_TYPE),
&cs40l20->f0_measured);
if (ret)
return ret;
ret = regmap_read(regmap, cs40l20_dsp_reg(cs40l20, "REDC",
CS40L20_XM_UNPACKED_TYPE),
&cs40l20->redc_measured);
if (ret)
return ret;
cs40l20->diag_state = CS40L20_DIAG_STATE_DONE;
return 0;
}
static void cs40l20_vibe_start_worker(struct work_struct *work)
{
struct cs40l20_private *cs40l20 =
container_of(work, struct cs40l20_private, vibe_start_work);
struct regmap *regmap = cs40l20->regmap;
struct device *dev = cs40l20->dev;
int ret;
mutex_lock(&cs40l20->lock);
/* gracefully exit if diagnostics stimulus is interrupted */
if (cs40l20->cp_trailer_index == CS40L20_INDEX_DIAG) {
cs40l20->diag_state = CS40L20_DIAG_STATE_INIT;
goto err_mutex;
}
cs40l20->cp_trailer_index = cs40l20->cp_trigger_index;
switch (cs40l20->cp_trailer_index) {
case 0x0000:
case 0x8000 ... 0xFFFE:
ret = regmap_write(regmap, CS40L20_MBOX_TRIGGER_MS,
cs40l20->cp_trailer_index & CS40L20_INDEX_MASK);
if (ret)
dev_err(dev, "Failed to start playback\n");
cs40l20->led_dev.brightness = LED_FULL;
break;
case 0x0001 ... 0x7FFF:
ret = regmap_write(regmap, CS40L20_MBOX_TRIGGERINDEX,
cs40l20->cp_trailer_index);
if (ret)
dev_err(dev, "Failed to start playback\n");
cs40l20->led_dev.brightness = LED_FULL;
break;
case CS40L20_INDEX_DIAG:
cs40l20->diag_state = CS40L20_DIAG_STATE_INIT;
ret = regmap_update_bits(cs40l20->regmap,
CS40L20_AMP_DIG_VOL_CTRL,
CS40L20_AMP_VOL_PCM_MASK, 0);
if (ret) {
dev_err(dev, "Failed to reset digital scale\n");
goto err_mutex;
}
ret = regmap_write(regmap,
cs40l20_dsp_reg(cs40l20, "CLOSED_LOOP",
CS40L20_XM_UNPACKED_TYPE),
0);
if (ret) {
dev_err(dev, "Failed to disable closed-loop mode\n");
goto err_mutex;
}
ret = regmap_write(regmap, CS40L20_MBOX_STIMULUS_MODE, 1);
if (ret) {
dev_err(dev, "Failed to enable stimulus mode\n");
goto err_mutex;
}
msleep(CS40L20_DIAG_STATE_DELAY_MS);
ret = regmap_write(regmap,
cs40l20_dsp_reg(cs40l20, "CLOSED_LOOP",
CS40L20_XM_UNPACKED_TYPE),
1);
if (ret) {
dev_err(dev, "Failed to enable closed-loop mode\n");
goto err_mutex;
}
cs40l20->diag_state = CS40L20_DIAG_STATE_RUN;
cs40l20->led_dev.brightness = LED_FULL;
break;
default:
dev_err(dev, "Invalid wavetable index\n");
}
err_mutex:
mutex_unlock(&cs40l20->lock);
}
static void cs40l20_vibe_stop_worker(struct work_struct *work)
{
struct cs40l20_private *cs40l20 =
container_of(work, struct cs40l20_private, vibe_stop_work);
struct regmap *regmap = cs40l20->regmap;
struct device *dev = cs40l20->dev;
int ret;
mutex_lock(&cs40l20->lock);
switch (cs40l20->cp_trailer_index) {
case CS40L20_INDEX_DIAG:
ret = cs40l20_diag_capture(cs40l20);
if (ret)
dev_err(dev, "Failed to capture f0 and ReDC\n");
ret = regmap_write(regmap, CS40L20_MBOX_STIMULUS_MODE, 0);
if (ret)
dev_err(dev, "Failed to disable stimulus mode\n");
ret = regmap_update_bits(cs40l20->regmap,
CS40L20_AMP_DIG_VOL_CTRL,
CS40L20_AMP_VOL_PCM_MASK,
((0x800 - cs40l20->dig_scale) & 0x7FF)
<< CS40L20_AMP_VOL_PCM_SHIFT);
if (ret)
dev_err(dev, "Failed to restore digital scale\n");
break;
default:
ret = regmap_write(regmap,
cs40l20_dsp_reg(cs40l20, "ENDPLAYBACK",
CS40L20_XM_UNPACKED_TYPE), 1);
if (ret)
dev_err(dev, "Failed to stop playback\n");
}
cs40l20->cp_trailer_index = 0;
cs40l20->led_dev.brightness = LED_OFF;
mutex_unlock(&cs40l20->lock);
}
/* vibration callback for LED device */
static void cs40l20_vibe_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct cs40l20_private *cs40l20 =
container_of(led_cdev, struct cs40l20_private, led_dev);
if (!cs40l20->vibe_workqueue || !cs40l20->vibe_init_success) {
dev_err(cs40l20->dev,
"Failed to set vibe when it's not ready\n");
return;
}
if (brightness == LED_OFF) {
/* If for any reasons the stop_work is too slow, the driver must
* wait till the previous stop_work has finished here or else
* the LRA might not stop potentially
*/
while (!queue_work(cs40l20->vibe_workqueue,
&cs40l20->vibe_stop_work))
udelay(1000);
} else {
queue_work(cs40l20->vibe_workqueue, &cs40l20->vibe_start_work);
}
}
static void cs40l20_create_led(struct cs40l20_private *cs40l20)
{
int ret;
struct led_classdev *led_dev = &cs40l20->led_dev;
struct device *dev = cs40l20->dev;
led_dev->name = CS40L20_DEVICE_NAME;
led_dev->max_brightness = LED_FULL;
led_dev->brightness_set = cs40l20_vibe_brightness_set;
led_dev->default_trigger = "transient";
led_dev->flags = LED_BRIGHTNESS_FAST;
ret = led_classdev_register(dev, led_dev);
if (ret) {
dev_err(dev, "Failed to register LED device: %d\n", ret);
return;
}
ret = sysfs_create_group(&cs40l20->dev->kobj, &cs40l20_dev_attr_group);
if (ret) {
dev_err(dev, "Failed to create sysfs group: %d\n", ret);
return;
}
}
static void cs40l20_vibe_init(struct cs40l20_private *cs40l20)
{
mutex_init(&cs40l20->lock);
cs40l20->vibe_workqueue =
alloc_ordered_workqueue("vibe_workqueue", WQ_HIGHPRI);
if (!cs40l20->vibe_workqueue) {
dev_err(cs40l20->dev, "Failed to allocate workqueue\n");
return;
}
INIT_WORK(&cs40l20->vibe_start_work, cs40l20_vibe_start_worker);
INIT_WORK(&cs40l20->vibe_stop_work, cs40l20_vibe_stop_worker);
cs40l20->vibe_init_success = true;
}
static int cs40l20_coeff_init(struct cs40l20_private *cs40l20)
{
int ret, i;
struct regmap *regmap = cs40l20->regmap;
struct device *dev = cs40l20->dev;
struct cs40l20_coeff_desc *coeff_desc;
unsigned int val, num_algos, algo_id, algo_rev;
unsigned int xm_base, xm_size, ym_base, ym_size;
unsigned int reg = CS40L20_XM_FW_ID;
ret = regmap_read(regmap, CS40L20_XM_NUM_ALGOS, &num_algos);
if (ret) {
dev_err(dev, "Failed to read number of algorithms\n");
return ret;
}
if (num_algos > CS40L20_NUM_ALGOS_MAX) {
dev_err(dev, "Invalid number of algorithms\n");
return -EINVAL;
}
/* add one extra iteration to account for system algorithm */
for (i = 0; i < (num_algos + 1); i++) {
ret = regmap_read(regmap,
reg + CS40L20_ALGO_ID_OFFSET, &algo_id);
if (ret) {
dev_err(dev, "Failed to read algo. %d ID\n", i);
return ret;
}
ret = regmap_read(regmap,
reg + CS40L20_ALGO_REV_OFFSET, &algo_rev);
if (ret) {
dev_err(dev, "Failed to read algo. %d revision\n", i);
return ret;
}
/* discern firmware revision from system algorithm */
if (i == 0) {
if (algo_rev < CS40L20_FW_REV_MIN) {
dev_err(dev,
"Invalid firmware revision: %d.%d.%d\n",
(algo_rev & 0xFF0000) >> 16,
(algo_rev & 0xFF00) >> 8,
algo_rev & 0xFF);
return -EINVAL;
}
dev_info(dev, "Firmware revision %d.%d.%d\n",
(algo_rev & 0xFF0000) >> 16,
(algo_rev & 0xFF00) >> 8, algo_rev & 0xFF);
}
ret = regmap_read(regmap, reg + CS40L20_ALGO_XM_BASE_OFFSET,
&xm_base);
if (ret) {
dev_err(dev, "Failed to read algo. %d XM_BASE\n", i);
return ret;
}
ret = regmap_read(regmap,
reg + CS40L20_ALGO_XM_SIZE_OFFSET, &xm_size);
if (ret) {
dev_err(dev, "Failed to read algo. %d XM_SIZE\n", i);
return ret;
}
ret = regmap_read(regmap,
reg + CS40L20_ALGO_YM_BASE_OFFSET, &ym_base);
if (ret) {
dev_err(dev, "Failed to read algo. %d YM_BASE\n", i);
return ret;
}
ret = regmap_read(regmap,
reg + CS40L20_ALGO_YM_SIZE_OFFSET, &ym_size);
if (ret) {
dev_err(dev, "Failed to read algo. %d YM_SIZE\n", i);
return ret;
}
list_for_each_entry(coeff_desc,
&cs40l20->coeff_desc_head, list) {
if (coeff_desc->parent_id != algo_id)
continue;
switch (coeff_desc->block_type) {
case CS40L20_XM_UNPACKED_TYPE:
coeff_desc->reg = CS40L20_DSP1_XMEM_UNPACK24_0
+ xm_base * 4
+ coeff_desc->block_offset * 4;
if (!strcmp(coeff_desc->name, "WAVETABLE")) {
cs40l20->wt_limit_xm = (xm_size
- coeff_desc->block_offset) * 4;
cs40l20->vibegen_id = algo_id;
cs40l20->vibegen_rev = algo_rev;
}
break;
case CS40L20_YM_UNPACKED_TYPE:
coeff_desc->reg = CS40L20_DSP1_YMEM_UNPACK24_0
+ ym_base * 4
+ coeff_desc->block_offset * 4;
if (!strcmp(coeff_desc->name, "WAVETABLEYM"))
cs40l20->wt_limit_ym = (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 += CS40L20_ALGO_ENTRY_SIZE;
else
reg += (CS40L20_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 != CS40L20_ALGO_LIST_TERM) {
dev_err(dev, "Invalid list terminator: 0x%X\n", val);
return -EINVAL;
}
dev_info(dev, "Maximum wavetable size: %d bytes (XM), %d bytes (YM)\n",
cs40l20->wt_limit_xm / 4 * 3, cs40l20->wt_limit_ym / 4 * 3);
return 0;
}
static void cs40l20_dsp_start(struct cs40l20_private *cs40l20)
{
int ret;
struct regmap *regmap = cs40l20->regmap;
struct device *dev = cs40l20->dev;
unsigned int val;
int dsp_timeout = CS40L20_DSP_TIMEOUT_COUNT;
switch (cs40l20->revid) {
case CS40L20_REVID_A0:
ret = regmap_update_bits(regmap, CS40L20_PWR_CTRL1,
CS40L20_GLOBAL_EN_MASK,
1 << CS40L20_GLOBAL_EN_SHIFT);
if (ret) {
dev_err(dev, "Failed to enable device\n");
return;
}
ret = regmap_update_bits(regmap, CS40L20_DSP1_CCM_CORE_CTRL,
CS40L20_DSP1_RESET_MASK |
CS40L20_DSP1_EN_MASK,
(1 << CS40L20_DSP1_RESET_SHIFT) |
(1 << CS40L20_DSP1_EN_SHIFT));
if (ret) {
dev_err(dev, "Failed to enable DSP\n");
return;
}
break;
default:
ret = regmap_update_bits(regmap, CS40L20_PWRMGT_CTL,
CS40L20_MEM_RDY_MASK,
1 << CS40L20_MEM_RDY_SHIFT);
if (ret) {
dev_err(dev, "Failed to set memory ready flag\n");
return;
}
ret = regmap_update_bits(regmap, CS40L20_DSP1_CCM_CORE_CTRL,
CS40L20_DSP1_RESET_MASK,
1 << CS40L20_DSP1_RESET_SHIFT);
if (ret) {
dev_err(dev, "Failed to restart DSP\n");
return;
}
}
while (dsp_timeout > 0) {
usleep_range(10000, 10100);
ret = regmap_read(regmap, cs40l20_dsp_reg(cs40l20, "HALO_STATE",
CS40L20_XM_UNPACKED_TYPE),
&val);
if (ret) {
dev_err(dev, "Failed to read DSP status\n");
return;
}
if (val == CS40L20_HALO_STATE_RUNNING)
break;
dsp_timeout--;
}
if (dsp_timeout == 0) {
dev_err(dev, "Timed out with DSP status = %d\n", val);
return;
}
dev_dbg(dev, "Haptics algorithm started\n");
ret = regmap_write(regmap, cs40l20_dsp_reg(cs40l20, "TIMEOUT_MS",
CS40L20_XM_UNPACKED_TYPE),
CS40L20_TIMEOUT_MS_MAX);
if (ret) {
dev_err(dev, "Failed to extend playback timeout\n");
return;
}
ret = regmap_read(regmap, cs40l20_dsp_reg(cs40l20, "NUMBEROFWAVES",
CS40L20_XM_UNPACKED_TYPE), &cs40l20->num_waves);
if (ret) {
dev_err(dev, "Failed to count wavetable entries\n");
return;
}
if (cs40l20->num_waves == 0) {
dev_err(dev, "Wavetable is empty\n");
return;
}
cs40l20_vibe_init(cs40l20);
}
static int cs40l20_raw_write(struct cs40l20_private *cs40l20, unsigned int reg,
const void *val, size_t val_len, size_t limit)
{
int ret = 0, i;
/* split "val" into smaller writes not to exceed "limit" in length */
for (i = 0; i < val_len; i += limit) {
ret = regmap_raw_write(cs40l20->regmap, (reg + i), (val + i),
(val_len - i) > limit ?
limit : (val_len - i));
if (ret)
break;
}
return ret;
}
static void cs40l20_waveform_load(const struct firmware *fw, void *context)
{
int ret;
struct cs40l20_private *cs40l20 = (struct cs40l20_private *)context;
struct device *dev = cs40l20->dev;
unsigned int pos = CS40L20_WT_FILE_HEADER_SIZE;
unsigned int block_type, block_length;
unsigned int algo_id, algo_rev;
if (!fw)
goto skip_loading;
if (memcmp(fw->data, "WMDR", 4)) {
dev_err(dev, "Failed to recognize waveform file\n");
goto err_rls_fw;
}
while (pos < fw->size) {
/* block offset is not used here */
pos += CS40L20_WT_DBLK_OFFSET_SIZE;
block_type = fw->data[pos]
+ (fw->data[pos + 1] << 8);
pos += CS40L20_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 += CS40L20_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 += CS40L20_WT_ALGO_REV_SIZE;
/* sample rate is not used here */
pos += CS40L20_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 += CS40L20_WT_DBLK_LENGTH_SIZE;
switch (block_type) {
case CS40L20_XM_UNPACKED_TYPE:
if (algo_id != cs40l20->vibegen_id) {
dev_err(dev, "Invalid algo. ID: 0x%06X\n",
algo_id);
goto err_rls_fw;
}
if (((algo_rev >> 8) & CS40L20_ALGO_REV_MASK)
!= (cs40l20->vibegen_rev
& CS40L20_ALGO_REV_MASK)) {
dev_err(dev, "Invalid algo. rev.: %d.%d.%d\n",
(algo_rev & 0xFF000000) >> 24,
(algo_rev & 0xFF0000) >> 16,
(algo_rev & 0xFF00) >> 8);
goto err_rls_fw;
}
if (block_length > cs40l20->wt_limit_xm) {
dev_err(dev,
"Wavetable too large: %d bytes (XM)\n",
block_length / 4 * 3);
goto err_rls_fw;
}
ret = cs40l20_raw_write(cs40l20,
cs40l20_dsp_reg(cs40l20, "WAVETABLE",
CS40L20_XM_UNPACKED_TYPE),
&fw->data[pos], block_length,
CS40L20_MAX_WLEN);
if (ret) {
dev_err(dev,
"Failed to write XM_UNPACKED memory\n");
goto err_rls_fw;
}
break;
case CS40L20_YM_UNPACKED_TYPE:
if (algo_id != cs40l20->vibegen_id) {
dev_err(dev, "Invalid algo. ID: 0x%06X\n",
algo_id);
goto err_rls_fw;
}
if (((algo_rev >> 8) & CS40L20_ALGO_REV_MASK)
!= (cs40l20->vibegen_rev
& CS40L20_ALGO_REV_MASK)) {
dev_err(dev, "Invalid algo. rev.: %d.%d.%d\n",
(algo_rev & 0xFF000000) >> 24,
(algo_rev & 0xFF0000) >> 16,
(algo_rev & 0xFF00) >> 8);
goto err_rls_fw;
}
if (block_length > cs40l20->wt_limit_ym) {
dev_err(dev,
"Wavetable too large: %d bytes (YM)\n",
block_length / 4 * 3);
goto err_rls_fw;
}
ret = cs40l20_raw_write(cs40l20,
cs40l20_dsp_reg(cs40l20, "WAVETABLEYM",
CS40L20_YM_UNPACKED_TYPE),
&fw->data[pos], block_length,
CS40L20_MAX_WLEN);
if (ret) {
dev_err(dev,
"Failed to write YM_UNPACKED memory\n");
goto err_rls_fw;
}
break;
}
pos += block_length;
}
skip_loading:
cs40l20_dsp_start(cs40l20);
err_rls_fw:
release_firmware(fw);
}
static int cs40l20_algo_parse(struct cs40l20_private *cs40l20,
const unsigned char *data)
{
struct cs40l20_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 += CS40L20_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 += CS40L20_COEFF_COUNT_SIZE;
for (i = 0; i < coeff_count; i++) {
block_offset = *(data + pos)
+ (*(data + pos + 1) << 8);
pos += CS40L20_COEFF_OFFSET_SIZE;
block_type = *(data + pos)
+ (*(data + pos + 1) << 8);
pos += CS40L20_COEFF_TYPE_SIZE;
block_length = *(data + pos)
+ (*(data + pos + 1) << 8)
+ (*(data + pos + 2) << 16)
+ (*(data + pos + 3) << 24);
pos += CS40L20_COEFF_LENGTH_SIZE;
coeff_desc = devm_kzalloc(cs40l20->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, &cs40l20->coeff_desc_head);
pos += block_length;
}
return 0;
}
static void cs40l20_firmware_load(const struct firmware *fw, void *context)
{
int ret;
struct cs40l20_private *cs40l20 = (struct cs40l20_private *)context;
struct device *dev = cs40l20->dev;
unsigned int pos = CS40L20_FW_FILE_HEADER_SIZE;
unsigned int block_offset, block_length;
unsigned char block_type;
if (!fw) {
dev_err(dev, "Failed to request firmware file\n");
return;
}
if (memcmp(fw->data, "WMFW", 4)) {
dev_err(dev, "Failed to recognize firmware file\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 += CS40L20_FW_DBLK_OFFSET_SIZE;
block_type = fw->data[pos];
pos += CS40L20_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 += CS40L20_FW_DBLK_LENGTH_SIZE;
switch (block_type) {
case CS40L20_PM_PACKED_TYPE:
ret = cs40l20_raw_write(cs40l20,
CS40L20_DSP1_PMEM_0
+ block_offset * 5,
&fw->data[pos], block_length,
CS40L20_MAX_WLEN);
if (ret) {
dev_err(dev,
"Failed to write PM_PACKED memory\n");
goto err_rls_fw;
}
break;
case CS40L20_XM_PACKED_TYPE:
ret = cs40l20_raw_write(cs40l20,
CS40L20_DSP1_XMEM_PACK_0
+ block_offset * 3,
&fw->data[pos], block_length,
CS40L20_MAX_WLEN);
if (ret) {
dev_err(dev,
"Failed to write XM_PACKED memory\n");
goto err_rls_fw;
}
break;
case CS40L20_YM_PACKED_TYPE:
ret = cs40l20_raw_write(cs40l20,
CS40L20_DSP1_YMEM_PACK_0
+ block_offset * 3,
&fw->data[pos], block_length,
CS40L20_MAX_WLEN);
if (ret) {
dev_err(dev,
"Failed to write YM_PACKED memory\n");
goto err_rls_fw;
}
break;
case CS40L20_ALGO_INFO_TYPE:
ret = cs40l20_algo_parse(cs40l20, &fw->data[pos]);
if (ret) {
dev_err(dev,
"Failed to parse algorithm: %d\n", ret);
goto err_rls_fw;
}
break;
}
pos += block_length;
}
ret = cs40l20_coeff_init(cs40l20);
if (ret)
goto err_rls_fw;
request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, CS40L20_WT_NAME,
dev, GFP_KERNEL, cs40l20, cs40l20_waveform_load);
err_rls_fw:
release_firmware(fw);
}
static int cs40l20_boost_config(struct cs40l20_private *cs40l20,
int boost_ind, int boost_cap, int boost_ipk)
{
int ret;
unsigned char bst_lbst_val, bst_cbst_range, bst_ipk_scaled;
struct regmap *regmap = cs40l20->regmap;
struct device *dev = cs40l20->dev;
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, CS40L20_BSTCVRT_COEFF,
CS40L20_BST_K1_MASK,
cs40l20_bst_k1_table[bst_lbst_val][bst_cbst_range]
<< CS40L20_BST_K1_SHIFT);
if (ret) {
dev_err(dev, "Failed to write boost K1 coefficient\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L20_BSTCVRT_COEFF,
CS40L20_BST_K2_MASK,
cs40l20_bst_k2_table[bst_lbst_val][bst_cbst_range]
<< CS40L20_BST_K2_SHIFT);
if (ret) {
dev_err(dev, "Failed to write boost K2 coefficient\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L20_BSTCVRT_SLOPE_LBST,
CS40L20_BST_SLOPE_MASK,
cs40l20_bst_slope_table[bst_lbst_val]
<< CS40L20_BST_SLOPE_SHIFT);
if (ret) {
dev_err(dev, "Failed to write boost slope coefficient\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L20_BSTCVRT_SLOPE_LBST,
CS40L20_BST_LBST_VAL_MASK,
bst_lbst_val << CS40L20_BST_LBST_VAL_SHIFT);
if (ret) {
dev_err(dev, "Failed to write 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, CS40L20_BSTCVRT_PEAK_CUR,
CS40L20_BST_IPK_MASK,
bst_ipk_scaled << CS40L20_BST_IPK_SHIFT);
if (ret) {
dev_err(dev, "Failed to write boost inductor peak current\n");
return ret;
}
return 0;
}
static const struct reg_sequence cs40l20_mpu_config[] = {
{CS40L20_DSP1_MPU_LOCK_CONFIG, CS40L20_MPU_UNLOCK_CODE1},
{CS40L20_DSP1_MPU_LOCK_CONFIG, CS40L20_MPU_UNLOCK_CODE2},
{CS40L20_DSP1_MPU_XM_ACCESS0, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_YM_ACCESS0, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_WNDW_ACCESS0, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_XREG_ACCESS0, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_YREG_ACCESS0, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_WNDW_ACCESS1, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_XREG_ACCESS1, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_YREG_ACCESS1, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_WNDW_ACCESS2, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_XREG_ACCESS2, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_YREG_ACCESS2, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_WNDW_ACCESS3, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_XREG_ACCESS3, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_YREG_ACCESS3, 0xFFFFFFFF},
{CS40L20_DSP1_MPU_LOCK_CONFIG, 0x00000000}
};
static int cs40l20_dsp_load(struct cs40l20_private *cs40l20)
{
int ret;
unsigned int revid = cs40l20->revid;
struct regmap *regmap = cs40l20->regmap;
struct device *dev = cs40l20->dev;
switch (revid) {
case CS40L20_REVID_A0:
case CS40L20_REVID_B0:
ret = regmap_multi_reg_write(regmap, cs40l20_mpu_config,
ARRAY_SIZE(cs40l20_mpu_config));
if (ret) {
dev_err(dev, "Failed to configure MPU\n");
return ret;
}
request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
CS40L20_FW_NAME_A0, dev, GFP_KERNEL, cs40l20,
cs40l20_firmware_load);
return 0;
case CS40L20_REVID_B1:
request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
CS40L20_FW_NAME_B1, dev, GFP_KERNEL, cs40l20,
cs40l20_firmware_load);
return 0;
default:
dev_err(dev, "No firmware defined for revision %02X\n", revid);
return -EINVAL;
}
}
static int cs40l20_init(struct cs40l20_private *cs40l20)
{
int ret;
struct regmap *regmap = cs40l20->regmap;
struct device *dev = cs40l20->dev;
/* REFCLK configuration is handled by revision B1 ROM */
if (cs40l20->pdata.refclk_gpio2 &&
(cs40l20->revid < CS40L20_REVID_B1)) {
ret = regmap_update_bits(regmap, CS40L20_GPIO_PAD_CONTROL,
CS40L20_GP2_CTRL_MASK,
CS40L20_GP2_CTRL_MCLK
<< CS40L20_GP2_CTRL_SHIFT);
if (ret) {
dev_err(dev, "Failed to select GPIO2 function\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L20_PLL_CLK_CTRL,
CS40L20_PLL_REFCLK_SEL_MASK,
CS40L20_PLL_REFCLK_SEL_MCLK
<< CS40L20_PLL_REFCLK_SEL_SHIFT);
if (ret) {
dev_err(dev, "Failed to select clock source\n");
return ret;
}
}
ret = cs40l20_boost_config(cs40l20, cs40l20->pdata.boost_ind,
cs40l20->pdata.boost_cap, cs40l20->pdata.boost_ipk);
if (ret)
return ret;
ret = regmap_update_bits(regmap, CS40L20_DAC_PCM1_SRC,
CS40L20_DAC_PCM1_SRC_MASK,
CS40L20_DAC_PCM1_SRC_DSP1TX1
<< CS40L20_DAC_PCM1_SRC_SHIFT);
if (ret) {
dev_err(dev, "Failed to route DSP to amplifier\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L20_DSP1_RX2_SRC,
CS40L20_DSP1_RXN_SRC_MASK,
CS40L20_DSP1_RXN_SRC_VMON
<< CS40L20_DSP1_RXN_SRC_SHIFT);
if (ret) {
dev_err(dev, "Failed to route voltage monitor to DSP\n");
return ret;
}
ret = regmap_update_bits(regmap, CS40L20_DSP1_RX3_SRC,
CS40L20_DSP1_RXN_SRC_MASK,
CS40L20_DSP1_RXN_SRC_IMON
<< CS40L20_DSP1_RXN_SRC_SHIFT);
if (ret) {
dev_err(dev, "Failed to route current monitor to DSP\n");
return ret;
}
cs40l20_create_led(cs40l20);
return 0;
}
static int cs40l20_otp_unpack(struct cs40l20_private *cs40l20)
{
struct regmap *regmap = cs40l20->regmap;
struct device *dev = cs40l20->dev;
struct cs40l20_trim trim;
unsigned char row_offset, col_offset;
unsigned int val, otp_map;
unsigned int otp_mem[CS40L20_NUM_OTP_WORDS];
int ret, i;
ret = regmap_read(cs40l20->regmap, CS40L20_OTPID, &val);
if (ret) {
dev_err(dev, "Failed to read OTP ID\n");
return ret;
}
/* hard matching against known OTP IDs */
for (i = 0; i < CS40L20_NUM_OTP_MAPS; i++) {
if (cs40l20_otp_map[i].id == val) {
otp_map = i;
break;
}
}
/* reject unrecognized IDs, including untrimmed devices (OTP ID = 0) */
if (i == CS40L20_NUM_OTP_MAPS) {
dev_err(dev, "Unrecognized OTP ID: 0x%01X\n", val);
return -ENODEV;
}
dev_dbg(dev, "Found OTP ID: 0x%01X\n", val);
ret = regmap_bulk_read(regmap, CS40L20_OTP_MEM0, otp_mem,
CS40L20_NUM_OTP_WORDS);
if (ret) {
dev_err(dev, "Failed to read OTP contents\n");
return ret;
}
ret = regmap_write(regmap, CS40L20_TEST_KEY_CTL,
CS40L20_TEST_KEY_UNLOCK_CODE1);
if (ret) {
dev_err(dev, "Failed to unlock test space (step 1 of 2)\n");
return ret;
}
ret = regmap_write(regmap, CS40L20_TEST_KEY_CTL,
CS40L20_TEST_KEY_UNLOCK_CODE2);
if (ret) {
dev_err(dev, "Failed to unlock test space (step 2 of 2)\n");
return ret;
}
row_offset = cs40l20_otp_map[otp_map].row_start;
col_offset = cs40l20_otp_map[otp_map].col_start;
for (i = 0; i < cs40l20_otp_map[otp_map].num_trims; i++) {
trim = cs40l20_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);
return ret;
}
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, CS40L20_TEST_KEY_CTL,
CS40L20_TEST_KEY_RELOCK_CODE1);
if (ret) {
dev_err(dev, "Failed to lock test space (step 1 of 2)\n");
return ret;
}
ret = regmap_write(regmap, CS40L20_TEST_KEY_CTL,
CS40L20_TEST_KEY_RELOCK_CODE2);
if (ret) {
dev_err(dev, "Failed to lock test space (step 2 of 2)\n");
return ret;
}
return 0;
}
static int cs40l20_handle_of_data(struct i2c_client *i2c_client,
struct cs40l20_platform_data *pdata)
{
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;
pdata->refclk_gpio2 = of_property_read_bool(np, "cirrus,refclk-gpio2");
return 0;
}
static const struct reg_sequence cs40l20_rev_a0_errata[] = {
{CS40L20_OTP_TRIM_30, 0x9091A1C8},
{CS40L20_PLL_MISC_CTRL, 0x0200EE0E},
{CS40L20_BSTCVRT_DCM_CTRL, 0x00000051},
{CS40L20_CTRL_ASYNC1, 0x00000004},
{CS40L20_IRQ1_DB3, 0x00000000},
{CS40L20_IRQ2_DB3, 0x00000000},
};
static int cs40l20_part_num_resolve(struct cs40l20_private *cs40l20)
{
struct regmap *regmap = cs40l20->regmap;
struct device *dev = cs40l20->dev;
unsigned int val, devid, revid;
unsigned int part_num_index;
int otp_timeout = CS40L20_OTP_TIMEOUT_COUNT;
int ret;
while (otp_timeout > 0) {
usleep_range(10000, 10100);
ret = regmap_read(regmap, CS40L20_IRQ1_STATUS4, &val);
if (ret) {
dev_err(dev, "Failed to read OTP boot status\n");
return ret;
}
if (val & CS40L20_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, CS40L20_IRQ1_STATUS3, &val);
if (ret) {
dev_err(dev, "Failed to read OTP error status\n");
return ret;
}
if (val & CS40L20_OTP_BOOT_ERR) {
dev_err(dev, "Encountered fatal OTP error\n");
return -EIO;
}
ret = regmap_read(regmap, CS40L20_DEVID, &devid);
if (ret) {
dev_err(dev, "Failed to read device ID\n");
return ret;
}
ret = regmap_read(regmap, CS40L20_REVID, &revid);
if (ret) {
dev_err(dev, "Failed to read revision ID\n");
return ret;
}
switch (devid) {
case CS40L20_DEVID_L20:
part_num_index = 0;
if (revid != CS40L20_REVID_A0)
goto err_revid;
ret = regmap_register_patch(regmap, cs40l20_rev_a0_errata,
ARRAY_SIZE(cs40l20_rev_a0_errata));
if (ret) {
dev_err(dev, "Failed to apply revision %02X errata\n",
revid);
return ret;
}
ret = cs40l20_otp_unpack(cs40l20);
if (ret)
return ret;
break;
case CS40L20_DEVID_L25:
part_num_index = 1;
if (revid != CS40L20_REVID_B0)
goto err_revid;
break;
case CS40L20_DEVID_L25A:
case CS40L20_DEVID_L25B:
part_num_index = devid - CS40L20_DEVID_L25A + 2;
if (revid < CS40L20_REVID_B1)
goto err_revid;
break;
default:
dev_err(dev, "Unrecognized device ID: 0x%06X\n", devid);
return -ENODEV;
}
dev_info(dev, "Cirrus Logic %s revision %02X\n",
cs40l20_part_nums[part_num_index], revid);
cs40l20->devid = devid;
cs40l20->revid = revid;
return 0;
err_revid:
dev_err(dev, "Unexpected revision ID for %s: %02X\n",
cs40l20_part_nums[part_num_index], revid);
return -ENODEV;
}
static struct regmap_config cs40l20_regmap = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.reg_format_endian = REGMAP_ENDIAN_BIG,
.val_format_endian = REGMAP_ENDIAN_BIG,
.max_register = CS40L20_LASTREG,
.precious_reg = cs40l20_precious_reg,
.readable_reg = cs40l20_readable_reg,
.cache_type = REGCACHE_NONE,
};
static int cs40l20_i2c_probe(struct i2c_client *i2c_client,
const struct i2c_device_id *id)
{
int ret, i;
struct cs40l20_private *cs40l20;
struct device *dev = &i2c_client->dev;
struct cs40l20_platform_data *pdata = dev_get_platdata(dev);
cs40l20 = devm_kzalloc(dev, sizeof(struct cs40l20_private), GFP_KERNEL);
if (!cs40l20)
return -ENOMEM;
cs40l20->dev = dev;
dev_set_drvdata(dev, cs40l20);
i2c_set_clientdata(i2c_client, cs40l20);
mutex_init(&cs40l20->lock);
INIT_LIST_HEAD(&cs40l20->coeff_desc_head);
cs40l20->regmap = devm_regmap_init_i2c(i2c_client, &cs40l20_regmap);
if (IS_ERR(cs40l20->regmap)) {
ret = PTR_ERR(cs40l20->regmap);
dev_err(dev, "Failed to allocate register map: %d\n", ret);
return ret;
}
for (i = 0; i < ARRAY_SIZE(cs40l20_supplies); i++)
cs40l20->supplies[i].supply = cs40l20_supplies[i];
cs40l20->num_supplies = ARRAY_SIZE(cs40l20_supplies);
ret = devm_regulator_bulk_get(dev, cs40l20->num_supplies,
cs40l20->supplies);
if (ret) {
dev_err(dev, "Failed to request core supplies: %d\n", ret);
return ret;
}
if (pdata) {
cs40l20->pdata = *pdata;
} else {
pdata = devm_kzalloc(dev, sizeof(struct cs40l20_platform_data),
GFP_KERNEL);
if (!pdata)
return -ENOMEM;
if (i2c_client->dev.of_node) {
ret = cs40l20_handle_of_data(i2c_client, pdata);
if (ret)
return ret;
}
cs40l20->pdata = *pdata;
}
ret = regulator_bulk_enable(cs40l20->num_supplies, cs40l20->supplies);
if (ret) {
dev_err(dev, "Failed to enable core supplies: %d\n", ret);
return ret;
}
cs40l20->reset_gpio = devm_gpiod_get_optional(dev, "reset",
GPIOD_OUT_LOW);
if (IS_ERR(cs40l20->reset_gpio))
return PTR_ERR(cs40l20->reset_gpio);
/* satisfy reset pulse width specification (with margin) */
usleep_range(2000, 2100);
gpiod_set_value_cansleep(cs40l20->reset_gpio, 1);
/* satisfy control port delay specification (with margin) */
usleep_range(1000, 1100);
ret = cs40l20_part_num_resolve(cs40l20);
if (ret)
goto err;
ret = cs40l20_init(cs40l20);
if (ret)
goto err;
ret = cs40l20_dsp_load(cs40l20);
if (ret)
goto err;
return 0;
err:
gpiod_set_value_cansleep(cs40l20->reset_gpio, 0);
regulator_bulk_disable(cs40l20->num_supplies, cs40l20->supplies);
return ret;
}
static int cs40l20_i2c_remove(struct i2c_client *i2c_client)
{
struct cs40l20_private *cs40l20 = i2c_get_clientdata(i2c_client);
if (cs40l20->vibe_init_success) {
led_classdev_unregister(&cs40l20->led_dev);
sysfs_remove_group(&cs40l20->dev->kobj,
&cs40l20_dev_attr_group);
cancel_work_sync(&cs40l20->vibe_start_work);
cancel_work_sync(&cs40l20->vibe_stop_work);
destroy_workqueue(cs40l20->vibe_workqueue);
}
gpiod_set_value_cansleep(cs40l20->reset_gpio, 0);
regulator_bulk_disable(cs40l20->num_supplies, cs40l20->supplies);
mutex_destroy(&cs40l20->lock);
return 0;
}
static const struct of_device_id cs40l20_of_match[] = {
{.compatible = "cirrus,cs40l20"},
{},
};
MODULE_DEVICE_TABLE(of, cs40l20_of_match);
static const struct i2c_device_id cs40l20_id[] = {
{"cs40l20", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, cs40l20_id);
static struct i2c_driver cs40l20_i2c_driver = {
.driver = {
.name = "cs40l20",
.of_match_table = cs40l20_of_match,
},
.id_table = cs40l20_id,
.probe = cs40l20_i2c_probe,
.remove = cs40l20_i2c_remove,
};
module_i2c_driver(cs40l20_i2c_driver);
MODULE_DESCRIPTION("CS40L20 Haptics Driver");
MODULE_AUTHOR("Jeff LaBundy, Cirrus Logic Inc, <jeff.labundy@cirrus.com>");
MODULE_LICENSE("GPL");