blob: d44537642fe12302808d3c94d5be6ada0b5688a8 [file] [log] [blame]
/*
* Audio * cs48L10.c -- CS48L10 DSP driver
*
* Copyright 2010 Cirrus Logic, Inc.
*
* Author: Paul Handrigan <Paul.Handrigan@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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#define DEBUG 1
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/regmap.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/firmware.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/time.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/hrtimer.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/cs48l10.h>
#include <linux/mfd/cirrus/crus_ctl_if.h>
#include "cs48l10.h"
#define CS48L10_WRITE_CMD 0x80
#define CS48L10_READ_CMD 0x81
#define CS48L10_SPI_BUSY_WAIT_USEC (100) /* usec */
#define CS48L10_SPI_INT_WAIT_MSEC (2) /* msec */
#define CS48L10_FIRMWARE_NAME "cirrus/cs48l10.bin"
#define PREKICKSTART_CFG 0x00
#define KICKSTART_CFG 0x01
#define INITIAL_CFG 0x02
/* Chas read/write mask */
#define CS48l10_RW_CMD_MASK 0x00C00000
/* Chas response codes */
#define CS48L10_BOOT_START 0x00000001
#define CS48L10_BOOT_SUCCESS 0x00000002
#define CS48L10_APP_START 0x00000004
#define CS48L10_BOOT_READY 0x00000005
#define CS48L10_DEFAULT_FIRMWARE_NAME "cirrus/cs48l10.bin"
#define CS48L10_SLEEP_SNAPSHOT_NAME "sleep"
#define CS48L10_WAKE_SNAPSHOT_NAME "wake"
#define CS48L10_MAX_SNAPSHOTS 100
#define FLASH_ADDRESS_INVALID (flash_address_t)0
#define FLASH_IMAGE_MARKER (u32)0x1A2B3C4D
/* slots for OS/MPM/VPM/PPM */
#define MAX_ULDS_PER_SET 5
/** maximum number of characters that can be used for display names.
*
* MAX_DISPLAY_LENGTH%sizeof(u32) must == 0, for checksum calculation to be correct
*/
#define MAX_DISPLAY_LENGTH 15
/* Address offset for the version id of the firmware. */
#define FW_VERSION_OFFSET 0x20
//#define CS48L10_NOBUSY
/* DSP Boot Loader Write Messages: */
static const u32 Slave_Boot = 0x80000000;
static const u32 Soft_Reset = 0x40000000;
static const u32 Soft_Boot[] = { 0x81000009, 0x00000001 };
/* Sleep Mode Command */
static const u32 Sleep_cmd[] = {0x81000009, 0x00000100};
/* Wake up from sleep Command */
static const u32 Wakeup_cmd[] = {0x81000009, 0x00000000};
/* Mute Command */
static const u32 Mute_cmd[] = {0x83000001, 0x00000001};
/* Unmute Command */
static const u32 Unmute_cmd[] = {0x83000001, 0x00000000};
/* a flash image pointer to a .cfg set of words. */
struct cfg_ptr {
u32 cfg_length;
u32 cfg_ptr;
};
struct uld_ptr{
u32 uld_length;
u32 uld_ptr;
};
/* all the information about a specific project uld set in flash image. */
struct uld_set{
struct uld_ptr uld_address[MAX_ULDS_PER_SET];
u32 memory_configuration;
u32 memory_map;
};
struct project_str {
u8 input_src;
u8 input_fs;
u8 output_fs;
u8 reserved;
struct uld_set uld_set;
struct cfg_ptr snapshot[]; /* first two snapshots are prekick and initial */
};
struct project_master_list_str {
u32 num_projects;
u32 cfgs_per_project;
u32 row_size;
struct project_str project;
};
/* data structure containing the displayable name for the project and all snapshots.
*
* Names for the prekick, kickstart, and initial snapshots are NOT considered displayable.
*/
struct project_display {
char project_name[MAX_DISPLAY_LENGTH+1];
char snapshot_name[CS48L10_MAX_SNAPSHOTS][MAX_DISPLAY_LENGTH+1];
};
/* the project display text master list. */
struct project_display_list {
u32 num_projects; /* should be the same as project_master_list.num_projects */
u32 max_snapshot_names; /* will be less than project master_list */
u32 row_size; /* bytes per row == bytes per project */
struct project_display display;
};
/* the initial index part of the flash image. */
struct updateable_flash_image_index {
/* start marker of the flash image. */
u32 start_marker;
/* address of the end marker for the flash image. */
u32 image_trailer_marker_ptr; /* image_trailer_marker_t* */
u32 image_checksum; /* not currently used. */
/* master pointer list of all ULDs in flash. */
u32 project_master_list_size; /* in bytes */
u32 project_master_list_ptr; /* project_master_list_t* */
/* address of text display structure. */
u32 project_display_ptr; /* project_text_display_t* */
/* words to use for HCMB boot message. If first word is zero, this is slave boot project */
u32 hcmb_message[2];
u32 fw_version_ptr;
};
struct cs48l10_firmware {
u32 firmware_size;
const u8 *firmware_data;
u8 current_project;
u8 current_snapshot;
u8 number_of_snapshots_for_current_project;
u8 active_project;
/* structure of flash addresses to DSP code (.uld) and configurations (.cfg) */
struct updateable_flash_image_index *pflash_image;
struct project_master_list_str *pmaster_list;
struct project_str *projects;
struct project_display_list *pdisplay_list;
struct project_display* project_names;
};
struct unsol_message
{
u32 data[2];
unsigned int timestamp_us;
struct list_head link;
};
/* cs48l10 private data */
struct cs48l10 {
struct spi_device *spi;
struct device *dev;
struct i2c_client *i2c;
struct cs48l10_platform_data pdata;
struct regmap *regmap;
const struct firmware *osfw; /* firmware class */
struct cs48l10_firmware *fw; /* parsed firmware */
struct mutex lock;
unsigned int irq;
struct clk *clk;
//hw access from apps and kernel
int (*read_word)(struct cs48l10 *cs48l10, u32 *buf);
int (*write_word)(struct cs48l10 *cs48l10, const u32 buf);
int (*write_word_buf)(struct cs48l10 *cs48l10, const u8 *buf, int size);
//hw access from CLIDE
int (*ctl_read_word)(struct cs48l10 *cs48l10, u32 *buf);
int (*ctl_write_word)(struct cs48l10 *cs48l10, const u32 buf);
int (*ctl_write_word_buf)(struct cs48l10 *cs48l10, const u8 *buf, int size);
/* sleep mode: Sleep mode if non zero */
int sleep_mode;
/* hibernate Mode. Hibernate mode if non-zero */
int hibernate;
wait_queue_head_t rx_data_wait;
int rx_data_available;
/* Unsolicited message handling */
int sol_count; /* Number of solicited messages waiting */
struct work_struct unsol_work;
struct list_head unsol_list;
int unsol_length;
spinlock_t sol_spinlock;
bool boot_assist;
int boot_assist_project;
bool fw_loaded;
#define FW_VERSION_SIZE 20
char firmware_version[FW_VERSION_SIZE];
u32 urd[MAX_URD_COUNT];
int urd_cnt;
bool reset;
int active;
};
static int cmp_micro_condenser_get_project_name
(
struct cs48l10 *cs48l10,
int project_id,
char buffer[]
);
/* cs48l10_spi_speed() - Set new SPI speed for all spi transactions */
static int cs48l10_spi_speed(struct spi_device* spi, int max_speed_hz)
{
int ret;
if (spi->max_speed_hz == max_speed_hz &&
spi->bits_per_word == 8 &&
spi->mode == SPI_MODE_0)
return 0;
if (max_speed_hz > CS48L10_MAX_SPI || max_speed_hz < CS48L10_MIN_SPI)
max_speed_hz = CS48L10_SPI_PREBOOT_FREQ;
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0; /* Normal clock, rising edge */
spi->max_speed_hz = max_speed_hz; /* Max clock is 24Mhz */
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "spi setup (%u Hz) failed\n",
max_speed_hz);
return ret;
}
dev_dbg(&spi->dev, "New SPI speed: %u Hz\n", max_speed_hz);
return 0;
}
static int cs48l10_i2c_write(struct i2c_client *i2c,
const u8 *src, int bytes)
{
unsigned char buf[bytes];
struct i2c_adapter *adap = i2c->adapter;
struct i2c_msg msg;
int ret;
memcpy(&buf[0], src, bytes);
msg.addr = i2c->addr;
msg.flags = 0;
msg.len = bytes;
msg.buf = buf;
ret = adap->algo->master_xfer(adap, &msg, 1);
if (ret < 0)
return ret;
return 0;
}
static int cs48l10_i2c_read(struct i2c_client *i2c,
void *dest, int bytes)
{
unsigned char buf[bytes];
struct i2c_adapter *adap = i2c->adapter;
struct i2c_msg msg;
if (dest == NULL)
return -EINVAL;
msg.addr = i2c->addr;
msg.flags = I2C_M_RD;
msg.len = bytes;
msg.buf = buf;
adap->algo->master_xfer(adap, &msg, 1);
memcpy(dest, buf, bytes);
return 0;
}
#ifndef CS48L10_NOBUSY
#define BUSY_RETRY 100
static void cs48l10_busy(struct cs48l10 *cs48l10)
{
int retry = BUSY_RETRY;
int dsp_busy;
if (!cs48l10->pdata.gpio_busy) {
udelay(CS48L10_SPI_BUSY_WAIT_USEC * 10);
return;
}
while (retry--) {
if ((dsp_busy = gpio_get_value(cs48l10->pdata.gpio_busy)) != 0)
return;
udelay(CS48L10_SPI_BUSY_WAIT_USEC);
}
dev_err(cs48l10->dev,"%s exhausted retries while waiting", __func__);
}
#endif //CS48L10_NOBUSY
static int cs48l10_write_dsp_word(struct cs48l10 *cs48l10, const u32 dsp_word)
{
u32 be32_word;
u8 buf[sizeof(be32_word)+1];
dev_dbg(cs48l10->dev,"%s %08x\n", __func__, dsp_word);
#ifndef CS48L10_NOBUSY
cs48l10_busy(cs48l10);
#endif
be32_word = cpu_to_be32(dsp_word);
if (cs48l10->spi) {
buf[0] = CS48L10_WRITE_CMD;
memcpy(&buf[1], &be32_word, sizeof(be32_word));
spi_write(cs48l10->spi, buf, sizeof(buf));
} else {
memcpy(&buf[0], &be32_word, sizeof(be32_word));
cs48l10_i2c_write(cs48l10->i2c, buf, sizeof(u32));
}
return 0;
}
static int cs48l10_read_dsp_word(struct cs48l10 *cs48l10, u32 * dsp_word)
{
struct spi_message msg;
struct spi_transfer tx, rx;
u32 srx = 0;
u8 stx = CS48L10_READ_CMD;
if (cs48l10->spi) {
memset(&tx, 0, sizeof(struct spi_transfer));
memset(&rx, 0, sizeof(struct spi_transfer));
/* tx xfer */
tx.tx_buf = &stx;
tx.len = sizeof(stx);
tx.bits_per_word = 8;
tx.speed_hz = cs48l10->spi->max_speed_hz;
/* rx xfer */
rx.rx_buf = &srx;
rx.len = sizeof(srx);
rx.bits_per_word = 8;
rx.speed_hz = cs48l10->spi->max_speed_hz;
spi_message_init(&msg);
spi_message_add_tail(&tx, &msg);
spi_message_add_tail(&rx, &msg);
spi_sync(cs48l10->spi, &msg);
} else {
cs48l10_i2c_read(cs48l10->i2c, &srx, sizeof(srx));
}
*dsp_word = be32_to_cpu(srx);
dev_dbg(cs48l10->dev,"%s %08x\n", __func__, *dsp_word);
return 0;
}
/*
* writes number of words which DSP can accept at once
* (no busy state while receiving those words)
*/
static int cs48l10_write_buf_nobusy_sz(struct cs48l10 *cs48l10,
const u8 * dsp_word_ptr,
int dsp_word_buf_len)
{
u8 buf[(DSP_WORD_TRASFER_SIZE * sizeof(u32)) + 1];
int buf_len = DSP_WORD_TRASFER_SIZE * sizeof(u32);
if (buf_len > dsp_word_buf_len)
buf_len = dsp_word_buf_len;
if (cs48l10->spi) {
buf[0] = CS48L10_WRITE_CMD;
memcpy(&buf[1], dsp_word_ptr, buf_len);
spi_write(cs48l10->spi, buf, buf_len+1);
} else {
cs48l10_i2c_write(cs48l10->i2c, dsp_word_ptr,
buf_len);
}
return 0;
}
static int cs48l10_write_buf(struct cs48l10 *cs48l10, const u8 * buf, int len)
{
int write_len = 0;
int chunk_len = DSP_WORD_TRASFER_SIZE * sizeof(u32);
len &= ~3;
write_len = len;
while (write_len) {
#ifndef CS48L10_NOBUSY
cs48l10_busy(cs48l10);
#endif
if (write_len < chunk_len)
chunk_len = write_len;
cs48l10_write_buf_nobusy_sz(cs48l10, buf, chunk_len);
write_len -= chunk_len;
buf += chunk_len;
}
return len;
}
static int cs48l10_write_then_read(struct cs48l10* cs48l10,
const u32* txwords, int txlen,
u32* rxwords, int rxlen)
{
int ret, rxread = 0;
unsigned long flags;
cs48l10->rx_data_available = 0;
/*Increment the sol count to show the isr how many soliciated
messages are expecting */
spin_lock_irqsave(&cs48l10->sol_spinlock, flags);
cs48l10->sol_count++;
spin_unlock_irqrestore(&cs48l10->sol_spinlock, flags);
dev_dbg(cs48l10->dev,"%s wcnt %d txw %08x\n", __func__, txlen, txlen ? *txwords : 0);
/* Send the DSP command words */
while (txlen--) {
if ((ret = cs48l10->write_word(cs48l10, *txwords)) < 0)
return ret;
txwords ++;
}
/* Wait for nINT */
ret = wait_event_interruptible_timeout(cs48l10->rx_data_wait,
!gpio_get_value(cs48l10->pdata.gpio_int), 1 * HZ);
if (ret < 0) {
dev_err(cs48l10->dev,
"%s(): Waiting for DSP response failed with err %d\n",
__func__, ret);
//return ret;
}
if (!ret) {
dev_err(cs48l10->dev,
"%s(): Timeout while waiting for dsp response\n",
__FUNCTION__);
//return -ETIMEDOUT;
}
//dev_dbg(cs48l10->dev,"%s got responce\n", __func__);
/* Read DSP response */
while (rxlen --) {
cs48l10->read_word(cs48l10, rxwords);
rxwords ++; rxread ++;
if (gpio_get_value(cs48l10->pdata.gpio_int))
break;
}
/*Decrement the sol count to show the isr how many soliciated messages
are expecting */
spin_lock_irqsave(&cs48l10->sol_spinlock, flags);
cs48l10->sol_count--;
spin_unlock_irqrestore(&cs48l10->sol_spinlock, flags);
cs48l10->rx_data_available = 0;
dev_dbg(cs48l10->dev,"%s rcnt %d rxw %08x\n", __func__, rxread, rxread ? *(--rxwords) : 0);
return rxread;
}
static int cs48l10_noop_read_word(struct cs48l10 *cs48l10, u32 *dsp_word)
{
return 0;
}
static int cs48l10_noop_write_word(struct cs48l10 *cs48l10, const u32 dsp_word)
{
return 0;
}
static int cs48l10_noop_write_buf(struct cs48l10 *cs48l10, const u8 *buf, int wcnt)
{
return 0;
}
/* CS48L10_nINT irq handler */
static irqreturn_t cs48l10_int_handler(int irq, void *data)
{
struct cs48l10 *cs48l10 = (struct cs48l10 *)data;
unsigned long flags;
int sol;
spin_lock_irqsave(&cs48l10->sol_spinlock, flags);
sol = cs48l10->sol_count;
spin_unlock_irqrestore(&cs48l10->sol_spinlock, flags);
cs48l10->rx_data_available = !gpio_get_value(cs48l10->pdata.gpio_int);
dev_dbg(cs48l10->dev, "INT sc=%d %s\n", sol, cs48l10->rx_data_available ? "":"NO DATA!?");
//WARN_ON(!cs48l10->rx_data_available);
/* Checks to see if the message is soliciated or not. If not,
* schedule the unsol message task.
*/
if (sol == 0)
schedule_work(&cs48l10->unsol_work);
else
wake_up_interruptible(&cs48l10->rx_data_wait);
return IRQ_HANDLED;
}
#define CS48L10_MAX_UNSOL_MESS 1
#define CS48L10_MAX_LIST_SIZE 50
/* Back end task for handling unsolicited messages on interrupt */
static void unsol_dsp_mess(struct work_struct *work)
{
int i;
u32 dsp_data1 = -1;
u32 dsp_data2 = -1;
struct unsol_message *umsg;
struct unsol_message *del_umsg;
struct cs48l10 *cs48l10 =
container_of(work, struct cs48l10, unsol_work);
dev_dbg(cs48l10->dev,"%s\n", __func__);
mutex_lock(&cs48l10->lock);
for(i = 0; i < CS48L10_MAX_UNSOL_MESS && !gpio_get_value(cs48l10->pdata.gpio_int); i++) {
umsg = kzalloc(sizeof(struct unsol_message),
GFP_KERNEL);
if (umsg == NULL) {
dev_err(cs48l10->dev,
"Cannot allocate memory in %s\n", __FUNCTION__);
return;
}
umsg->timestamp_us = jiffies_to_msecs(jiffies);
/* Unsolicited messages are two 32 bit words. */
cs48l10->read_word(cs48l10, &dsp_data1);
if (!gpio_get_value(cs48l10->pdata.gpio_int))
cs48l10->read_word(cs48l10, &dsp_data2);
umsg->data[0] = dsp_data1;
umsg->data[1] = dsp_data2;
dev_dbg(cs48l10->dev,"%s dsp_w1 %08x dsp_w2 %08x\n", __func__, dsp_data1, dsp_data2);
if(cs48l10->unsol_length >= CS48L10_MAX_LIST_SIZE) {
/*Remove the first element */
WARN_ON(list_empty(&cs48l10->unsol_list));
if (!list_empty(&cs48l10->unsol_list)) {
del_umsg = list_first_entry(&cs48l10->unsol_list,
struct unsol_message, link);
list_del(&del_umsg->link);
kfree(del_umsg);
}
} else {
cs48l10->unsol_length++;
}
list_add_tail(&umsg->link, &cs48l10->unsol_list);
}
/* Send event to user space */
//kobject_uevent(&spi_dev->dev.kobj, KOBJ_CHANGE);
mutex_unlock(&cs48l10->lock);
}
/* DSP hardware reset */
static int cs48l10_dsp_reset(struct cs48l10 *cs48l10)
{
int ret;
u32 dsp_result;
unsigned long flags;
if (!cs48l10->pdata.gpio_nreset)
return -EINVAL;
dev_dbg(cs48l10->dev, "%s RESET\n", __func__);
/* Increment the sol count to show the isr how many soliciated messages
* are expecting
*/
spin_lock_irqsave(&cs48l10->sol_spinlock, flags);
cs48l10->sol_count++;
spin_unlock_irqrestore(&cs48l10->sol_spinlock, flags);
gpio_set_value(cs48l10->pdata.gpio_nreset, 0);
/* After reset, chip is no longer in hibernate or sleep mode */
cs48l10->hibernate = 0;
cs48l10->sleep_mode = 0;
cs48l10->reset = true;
if (cs48l10->spi) {
ret = gpio_direction_output(cs48l10->pdata.gpio_busy, 1);
if (ret) {
dev_err(cs48l10->dev, "%s request for gpio %d direction failed with %d\n", __func__, cs48l10->pdata.gpio_busy, ret);
return ret;
}
ret = gpio_direction_output(cs48l10->pdata.gpio_int, 1);
if (ret) {
dev_err(cs48l10->dev, "%s request for gpio %d direction failed with %d\n", __func__, cs48l10->pdata.gpio_busy, ret);
return ret;
}
gpio_set_value(cs48l10->pdata.gpio_int, 1);
gpio_set_value(cs48l10->pdata.gpio_busy, 1);
}
udelay(500);
gpio_set_value(cs48l10->pdata.gpio_nreset, 1);
if (cs48l10->spi) {
ret = gpio_direction_input(cs48l10->pdata.gpio_busy);
if (ret) {
dev_err(cs48l10->dev, "%s request for gpio %d direction failed with %d\n", __func__, cs48l10->pdata.gpio_busy, ret);
return ret;
}
ret = gpio_direction_input(cs48l10->pdata.gpio_int);
if (ret) {
dev_err(cs48l10->dev, "%s request for gpio %d direction failed with %d\n", __func__, cs48l10->pdata.gpio_busy, ret);
return ret;
}
}
ret = wait_event_interruptible_timeout(cs48l10->rx_data_wait,
!gpio_get_value(cs48l10->pdata.gpio_int), 5 * HZ);
if (ret < 0) {
dev_err(cs48l10->dev,
"%s(): Waiting for BOOT_READY failed with err %d\n",
__func__, ret);
return ret;
}
if (!ret) {
dev_err(cs48l10->dev,
"%s(): Timeout while waiting for BOOT_READY (%08x)\n",
__FUNCTION__, CS48L10_BOOT_READY);
return -ETIMEDOUT;
}
/* Decrement the sol count to show the isr how many soliciated
* messages are expecting.
*/
spin_lock_irqsave(&cs48l10->sol_spinlock, flags);
cs48l10->sol_count--;
spin_unlock_irqrestore(&cs48l10->sol_spinlock, flags);
cs48l10->read_word(cs48l10, &dsp_result);
/* BOOT_READY expected */
if (dsp_result != CS48L10_BOOT_READY) {
dev_err(cs48l10->dev,
"%s DSP result = 0x%08x, expected %08x\n",
__func__, dsp_result, CS48L10_BOOT_READY);
return -EIO;
}
return 0;
}
/*
Cirrus Logic Microcondenser Firmware Loader
*/
static int cs48l10_fw_validate_ptr(struct cs48l10_firmware *fw,
u32 ptr)
{
return (ptr >= fw->firmware_size);
}
/*Loads a configuration/snapshot using the number specified. */
static int cs48l10_fw_load_config(struct cs48l10 *cs48l10, int cfg)
{
struct cs48l10_firmware *fw = cs48l10->fw;
int ret;
u32 cfg_addr;
u32 cfg_ptr_addr;
struct cfg_ptr *pcfg;
struct project_master_list_str *master_list;
struct project_str *project;
const u8 *buf;
u32 bufsize;
/* get Snapshot[cfg] ptr and address from flash, only 1 snapshot ptr and
address is saved in the MCU RAM. */
master_list = (struct project_master_list_str *)
(fw->firmware_data
+ cpu_to_be32(fw->pflash_image->project_master_list_ptr));
project = (struct project_str *)(((u8 *)&master_list->project) +
( be32_to_cpu(fw->pmaster_list->row_size) * fw->current_project));
pcfg = (struct cfg_ptr *)&project->snapshot[cfg];
cfg_addr = (u8 *)pcfg - (u8 *)fw->firmware_data;
if (cs48l10_fw_validate_ptr(fw, cfg_addr))
return -EIO;
bufsize = be32_to_cpu(project->snapshot[cfg].cfg_length);
cfg_ptr_addr = be32_to_cpu(project->snapshot[cfg].cfg_ptr);
if (cs48l10_fw_validate_ptr(fw, cfg_ptr_addr + bufsize))
return -EIO;
if (bufsize && cfg_ptr_addr) {
buf = fw->firmware_data + cfg_ptr_addr;
if ((ret = cs48l10->write_word_buf(cs48l10, buf, bufsize)) < 0)
return ret;
}
fw->current_snapshot = cfg;
dev_dbg(cs48l10->dev, "CFG[%d] for project[%d] %d bytes [OK]\n",
fw->current_snapshot, fw->current_project, bufsize);
return 0;
}
/* Fills a buffer with the name of a specific project */
static int cmp_micro_condenser_get_project_name(struct cs48l10 *cs48l10,
int project_id, char *buffer)
{
int flash_addr;
int row_size;
struct project_display_list * display_info;
struct project_display * project_info;
struct cs48l10_firmware *fw = cs48l10->fw;
/* Fill the display_list data structure if it exists */
if (fw->pflash_image->project_display_ptr) {
flash_addr =
be32_to_cpu(fw->pflash_image->project_display_ptr);
if(cs48l10_fw_validate_ptr(fw, flash_addr )) {
printk(KERN_INFO "flash_addr check failed \n");
return -EIO;
}
display_info = (struct project_display_list *)
(((u8 *)cs48l10->fw->firmware_data) + flash_addr);
row_size = be32_to_cpu(display_info->row_size);
project_info = (struct project_display *)
(( u8 *) &display_info->display + (row_size * project_id));
memcpy(buffer, project_info->project_name, MAX_DISPLAY_LENGTH);
} else {
sprintf(buffer,"%2d", project_id);
}
return 0;
}
/* Fills a buffer with the name of the snapshot */
static int cmp_micro_condenser_get_snapshot_name(struct cs48l10 *cs48l10,
int project_id, int snapshot_id, char *buffer)
{
int flash_addr;
int row_size;
int max_snapshot_names;
struct project_display_list *display_info;
struct project_display *project_info;
struct cs48l10_firmware *fw = cs48l10->fw;
if (snapshot_id < INITIAL_CFG){
sprintf((char *)buffer,"%2d", snapshot_id);
} else if (snapshot_id == INITIAL_CFG) {
sprintf ((char *)buffer, "Initial");
} else if (fw->pflash_image->project_display_ptr) {
flash_addr = be32_to_cpu(fw->pflash_image->project_display_ptr);
/* Get the row size. */
display_info = (struct project_display_list *)
( ((u8 *)cs48l10->fw->firmware_data) + flash_addr);
row_size = be32_to_cpu(display_info->row_size);
/* Get the max number of snapshots per project. */
max_snapshot_names =
be32_to_cpu(display_info->max_snapshot_names);
/* We need to use max_snapshot_names+3 here because the first
*three snapshots are pre-kickstart, kickstart, and initial
*/
if(snapshot_id > (max_snapshot_names + 3)) {
printk("Invalid snapshot id %d\n", snapshot_id);
sprintf((char *)buffer,"%2d", project_id);
return -EIO;
}
project_info = (struct project_display *)
((u8*) &display_info->display +
(row_size * project_id));
/* We need to use snapshot_id-3 here because the first three
* snapshots are pre-kickstart, kickstart, and initial
*/
memcpy(buffer, project_info->snapshot_name[snapshot_id-3],
MAX_DISPLAY_LENGTH);
} else {
sprintf(buffer,"%2d", snapshot_id);
}
return 0;
}
/* This function boots the uld at the specified firmware offset. */
static int cs48l10_fw_slave_boot_uld(struct cs48l10 *cs48l10, u32 addr,
u32 size)
{
struct cs48l10_firmware *fw = cs48l10->fw;
int ret;
const u8 *buf;
u32 dsp_result;
unsigned long flags;
/* Send SLAVE_BOOT message */
spin_lock_irqsave(&cs48l10->sol_spinlock, flags);
cs48l10->sol_count++;
spin_unlock_irqrestore(&cs48l10->sol_spinlock, flags);
if ((ret = cs48l10_write_then_read(cs48l10,
&Slave_Boot, 1,
&dsp_result, 1)) < 0)
return ret;
/* BOOT_START expected */
if (dsp_result != CS48L10_BOOT_START) {
dev_err(cs48l10->dev,
"DSP result = 0x%08x, expected %08x\n", dsp_result,
CS48L10_BOOT_START);
return -EIO;
}
dev_dbg(cs48l10->dev, "\t@ %08x, size %d\n", addr, size);
if (cs48l10_fw_validate_ptr(fw, addr + size))
return -EIO;
buf = fw->firmware_data + addr;
cs48l10->rx_data_available = 0;
if ((ret = cs48l10->write_word_buf(cs48l10, buf, size)) < 0)
return ret;
/* Increment the sol count to show the isr how many soliciated messages
* are expecting.
*/
ret = wait_event_interruptible_timeout(cs48l10->rx_data_wait,
cs48l10->rx_data_available, 5 * HZ);
if (ret < 0) {
dev_err(cs48l10->dev,
"%s(): Waiting for DSP response failed with err %d\n",
__func__, ret);
return ret;
}
if (!ret) {
dev_err(cs48l10->dev,
"%s(): Timeout while waiting for dsp response\n",
__FUNCTION__);
return -ETIMEDOUT;
}
/* Decrement the sol count to show the isr how many soliciated
* messages are expecting.
*/
spin_lock_irqsave(&cs48l10->sol_spinlock, flags);
cs48l10->sol_count--;
spin_unlock_irqrestore(&cs48l10->sol_spinlock, flags);
/* Read DSP response */
cs48l10->read_word(cs48l10, &dsp_result);
/* BOOT_SUCCESS expected */
if (dsp_result != CS48L10_BOOT_SUCCESS) {
dev_err(cs48l10->dev,
"DSP result = 0x%08x, expected %08x\n", dsp_result,
CS48L10_BOOT_SUCCESS);
return -EIO;
}
dev_dbg(cs48l10->dev, "\t@ %08x, size %d [OK]\n", addr, size);
return 0;
}
static int cs48l10_fw_load_project(struct cs48l10 *cs48l10, int project_id,
int snapshot_id)
{
struct cs48l10_firmware *fw = cs48l10->fw;
int ret=0, i;
struct project_str *pproject;
struct project_display *pproject_name;
u32 dsp_result = 0;
pproject= (struct project_str *)((u8 *) fw->projects +
cpu_to_be32(fw->pmaster_list->row_size) * project_id);
pproject_name = (struct project_display *)((u8 *) fw->project_names +
cpu_to_be32(fw->pdisplay_list->row_size) * project_id);
dev_dbg(cs48l10->dev, "Project[%d] %s\n", project_id,
pproject_name->project_name);
dev_dbg(cs48l10->dev, "Project Input Src %02x\n",
pproject->input_src);
dev_dbg(cs48l10->dev, "Project Input FS %02x\n",
pproject->input_fs);
dev_dbg(cs48l10->dev, "Project Output FS %02x\n",
pproject->output_fs);
for (i = 0; i < MAX_ULDS_PER_SET; i++) {
if (pproject->uld_set.uld_address[i].uld_length == 0)
continue;
dev_dbg(cs48l10->dev, "ULD[%d]\n", i);
if ((ret = cs48l10_fw_slave_boot_uld(cs48l10,
cpu_to_be32(pproject->
uld_set.
uld_address[i].
uld_ptr),
cpu_to_be32(pproject->
uld_set.
uld_address[i].
uld_length))) <
0)
return ret;
}
/* Send SOFT_RESET message */
if ((ret = cs48l10_write_then_read(cs48l10,
&Soft_Reset, 1, &dsp_result, 1)) < 0)
return ret;
if (dsp_result == CS48L10_BOOT_READY && cs48l10->boot_assist == true) {
/* We are in boot assist. We are done here.*/
return ret;
} else if (dsp_result != CS48L10_APP_START) {
dev_err(cs48l10->dev,
"DSP result = 0x%08x, expected %08x\n", dsp_result,
CS48L10_APP_START);
return -EIO;
}
/* If not in boot assist and CS48L10_APP_START is true,
* then we load the approprite project.
*/
fw->current_project = project_id;
fw->current_snapshot = snapshot_id;
if (cs48l10->spi)
ret = cs48l10_spi_speed(cs48l10->spi,
CS48L10_SPI_RUNTIME_FREQ);
if (ret < 0)
return ret;
if ((ret = cs48l10_fw_load_config(cs48l10, PREKICKSTART_CFG)) < 0)
return ret;
if ((ret = cs48l10_fw_load_config(cs48l10, INITIAL_CFG)) < 0)
return ret;
if(snapshot_id != INITIAL_CFG)
{
if ((ret = cs48l10_fw_load_config(cs48l10, snapshot_id)) < 0)
return ret;
}
if ((ret = cs48l10_fw_load_config(cs48l10, KICKSTART_CFG)) < 0)
return ret;
fw->active_project = project_id;
mdelay(5);
dev_dbg(cs48l10->dev, "%s Project[%d] %s Snapshot[%d] [OK]\n", __func__, project_id,
pproject_name->project_name, snapshot_id);
cs48l10->reset = false;
return 0;
}
static int cs48l10_fw_softboot_project(struct cs48l10 *cs48l10,
int project_id, int snapshot_id)
{
struct cs48l10_firmware *fw = cs48l10->fw;
int ret;
u32 dsp_result;
if (!fw)
return -EINVAL;
if (project_id >= cpu_to_be32(fw->pmaster_list->num_projects)) {
dev_err(cs48l10->dev,
"Project %d is not in projects master list\n",
project_id);
return -EINVAL;
}
if ((ret = cs48l10_write_then_read(cs48l10,
Soft_Boot, ARRAY_SIZE(Soft_Boot),
&dsp_result, 1)) < 0)
return ret;
if (dsp_result != CS48L10_BOOT_READY) {
dev_err(cs48l10->dev,
"DSP result = 0x%08x, expected %08x\n", dsp_result,
CS48L10_BOOT_READY);
return -EIO;
}
return cs48l10_fw_load_project(cs48l10, project_id, snapshot_id);
}
static int cs48l10_fw_boot_project(struct cs48l10 *cs48l10, int project_id,
int snapshot_id)
{
struct cs48l10_firmware *fw = cs48l10->fw;
int ret = 0;
if (!fw) {
dev_err(cs48l10->dev,
"Error occurred in cs48l10_boot_project\n");
return -EINVAL;
}
if (project_id >= cpu_to_be32(fw->pmaster_list->num_projects)) {
dev_err(cs48l10->dev,
"Project %d is not in projects master list\n",
project_id);
return -EINVAL;
}
if (cs48l10->spi)
ret = cs48l10_spi_speed(cs48l10->spi,
CS48L10_SPI_PREBOOT_FREQ);
if (ret < 0)
return ret;
if ((ret = cs48l10_dsp_reset(cs48l10)) < 0) {
dev_err(cs48l10->dev,
"Error loading project [%d]. DSP reset failed\n",
project_id);
return ret;
}
return cs48l10_fw_load_project(cs48l10, project_id, snapshot_id);
}
/* Performs a hard reset, loads the boot assist project, and loads the
* default project.
*/
static int cs48l10_fw_bootassist_project(struct cs48l10 *cs48l10, int project_id, int snapshot_id)
{
struct cs48l10_firmware *fw = cs48l10->fw;
int ret = 0;
if (!fw) {
dev_err(cs48l10->dev,
"Error occurred in cs48l10_boot_project\n");
return -EINVAL;
}
if (project_id >= cpu_to_be32(fw->pmaster_list->num_projects)) {
dev_err(cs48l10->dev,
"Project %d is not in projects master list\n",
project_id);
return -EINVAL;
}
/* The initial spi speed should be set as a fraction of the
* reference clock speed.
*/
if (cs48l10->spi)
ret = cs48l10_spi_speed(cs48l10->spi,
CS48L10_SPI_PREBOOT_FREQ);
if (ret < 0)
return ret;
if ((ret = cs48l10_dsp_reset(cs48l10)) < 0) {
dev_err(cs48l10->dev,
"Error loading project [%d]. DSP reset failed\n",
project_id);
return ret;
}
/* Load the boot assist project */
ret = cs48l10_fw_load_project(cs48l10, cs48l10->boot_assist_project,
INITIAL_CFG);
if(ret < 0 )
return ret;
/* The PLL in the dsp should be locked. We now can use the max
* frequency of the SPI bus.
*/
if (cs48l10->spi)
ret = cs48l10_spi_speed(cs48l10->spi,
CS48L10_SPI_BOOT_FREQ);
if (ret < 0)
return ret;
cs48l10->boot_assist = false;
/* Now, load the required project */
return cs48l10_fw_load_project(cs48l10, project_id, snapshot_id);
}
static int cs48l10_fw_parse_master_list(struct cs48l10 *cs48l10)
{
struct cs48l10_firmware *fw = cs48l10->fw;
u32 projects_addr, project_addr_last;
u32 project_names_addr, project_names_addr_last;
fw->current_project = 0;
fw->active_project = 0;
projects_addr = cpu_to_be32(fw->pflash_image->project_master_list_ptr) +
sizeof(struct project_master_list_str) - sizeof(struct project_str);
if (cs48l10_fw_validate_ptr(fw, projects_addr))
return -EIO;
project_addr_last = projects_addr +
(cpu_to_be32(fw->pmaster_list->row_size) *
(cpu_to_be32(fw->pmaster_list->num_projects) - 1));
if (cs48l10_fw_validate_ptr(fw, project_addr_last))
return -EIO;
fw->projects = (struct project_str *) (fw->firmware_data + projects_addr);
project_names_addr = cpu_to_be32(fw->pflash_image->project_display_ptr) +
sizeof(struct project_display_list) - sizeof(struct project_display);
if (cs48l10_fw_validate_ptr(fw, project_names_addr))
return -EIO;
project_names_addr_last = project_names_addr +
(cpu_to_be32(fw->pdisplay_list->row_size) *
(cpu_to_be32(fw->pdisplay_list->num_projects) - 1));
if (cs48l10_fw_validate_ptr(fw, project_names_addr_last))
return -EIO;
fw->project_names = (struct project_display *) (
fw->firmware_data + project_names_addr);
return 0;
}
static int cs48l10_fw_parse_firmware(struct cs48l10 *cs48l10)
{
struct cs48l10_firmware *fw = cs48l10->fw;
u32 image_marker;
fw->firmware_data = cs48l10->osfw->data;
fw->firmware_size = cs48l10->osfw->size;
if (cs48l10_fw_validate_ptr(fw, sizeof(struct updateable_flash_image_index)))
return -EINVAL;
fw->pflash_image = (struct updateable_flash_image_index *) fw->firmware_data;
dev_dbg(cs48l10->dev,
"Start Marker: %08x\n",
cpu_to_be32(fw->pflash_image->start_marker));
if (FLASH_IMAGE_MARKER != cpu_to_be32(fw->pflash_image->start_marker)) {
dev_dbg(cs48l10->dev, "Bad start Marker\n");
return -EIO;
}
if (cs48l10_fw_validate_ptr
(fw, cpu_to_be32(fw->pflash_image->image_trailer_marker_ptr)))
return -EIO;
image_marker = *((u32 *) (fw->firmware_data +
cpu_to_be32(fw->pflash_image->
image_trailer_marker_ptr)));
if (cpu_to_be32(image_marker) != FLASH_IMAGE_MARKER) {
dev_err(cs48l10->dev, "Bad trailer Marker %08x\n",
cpu_to_be32(image_marker));
return -EIO;
}
dev_dbg(cs48l10->dev, "Trailer Marker Ptr: %08x\n",
cpu_to_be32(fw->pflash_image->image_trailer_marker_ptr));
dev_dbg(cs48l10->dev,
"Image Chekcsum: %08x\n",
cpu_to_be32(fw->pflash_image->image_checksum));
dev_dbg(cs48l10->dev, "Project Master List Size: %08x\n",
cpu_to_be32(fw->pflash_image->project_master_list_size));
dev_dbg(cs48l10->dev, "Project Master Ptr: %08x\n",
cpu_to_be32(fw->pflash_image->project_master_list_ptr));
dev_dbg(cs48l10->dev, "Project Text Display Ptr: %08x\n",
cpu_to_be32(fw->pflash_image->project_display_ptr));
dev_dbg(cs48l10->dev,
"HCMB[0,1]: %08x, %08x\n",
cpu_to_be32(fw->
pflash_image->hcmb_message
[0]),
cpu_to_be32(fw->pflash_image->hcmb_message[1]));
if (cs48l10_fw_validate_ptr
(fw, cpu_to_be32(fw->pflash_image->project_master_list_ptr)))
return -EINVAL;
fw->pmaster_list = (struct project_master_list_str *)
(fw->firmware_data +
cpu_to_be32(fw->pflash_image->project_master_list_ptr));
dev_dbg(cs48l10->dev, "Master List\n");
dev_dbg(cs48l10->dev, "Number of Projects: %d\n",
cpu_to_be32(fw->pmaster_list->num_projects));
dev_dbg(cs48l10->dev, "Max Cfgs per Project: %d\n",
cpu_to_be32(fw->pmaster_list->cfgs_per_project));
dev_dbg(cs48l10->dev, "Row size: %d\n",
cpu_to_be32(fw->pmaster_list->row_size));
fw->pdisplay_list = (struct project_display_list *)
(fw->firmware_data +
cpu_to_be32(fw->pflash_image->project_display_ptr));
dev_dbg(cs48l10->dev, "Display List\n");
dev_dbg(cs48l10->dev, "Number of Projects(display): %d\n",
cpu_to_be32(fw->pdisplay_list->num_projects));
dev_dbg(cs48l10->dev, "Max snapshots per Project: %d\n",
cpu_to_be32(fw->pdisplay_list->max_snapshot_names));
dev_dbg(cs48l10->dev, "Row size: %d\n",
cpu_to_be32(fw->pdisplay_list->row_size));
strncpy(cs48l10->firmware_version, fw->firmware_data +
FW_VERSION_OFFSET, FW_VERSION_SIZE);
cs48l10_fw_parse_master_list(cs48l10);
return 0;
}
/* Microcondenser Firmware Loader */
//static int cs48l10_fw_load_sync(struct cs48l10 *cs48l10, const char *filename)
//{
// int ret;
// int i;
// struct cs48l10_firmware *fw;
// struct project_display *pproject_name;
//
// dev_dbg(cs48l10->dev, "cs48l10_fw_load_sync() ...\n");
// if (!cs48l10)
// return -ENOMEM;
//
// if (cs48l10->fw)
// kfree(cs48l10->fw);
//
// if (cs48l10->osfw)
// release_firmware(cs48l10->osfw);
//
// ret = request_firmware(&cs48l10->osfw, filename, &cs48l10->dev);
// if (ret != 0) {
// dev_err(&cs48l10->dev,
// "firmware: %s not found. err = %d\n", filename, ret);
// return ret;
// }
//
// cs48l10->fw = (struct cs48l10_firmware *)
// kzalloc(sizeof(struct cs48l10_firmware), GFP_KERNEL);
//
// if (!cs48l10->fw) {
// release_firmware(cs48l10->osfw);
// cs48l10->osfw = NULL;
// return -ENOMEM;
// }
// ret = cs48l10_fw_parse_firmware(cs48l10);
// if (ret < 0) {
// dev_err(cs48l10->dev,
// "firmware (%u bytes) failed to load, ret = %d\n",
// cs48l10->osfw->size, ret);
//
// kfree(cs48l10->fw);
// release_firmware(cs48l10->osfw);
// cs48l10->osfw = NULL;
// cs48l10->fw = NULL;
// return -EINVAL;
// }
//
// dev_dbg(cs48l10->dev,
// "firmware (%u bytes) parsed successfully\n",
// cs48l10->osfw->size);
//
//
// fw = cs48l10->fw;
// /*Set boot_assist_project to < 0 to identify that it was not set. */
// cs48l10->boot_assist_project = -1;
// cs48l10->boot_assist = false;
//
// for(i = 0; i < cpu_to_be32(fw->pmaster_list->num_projects); i++) {
// pproject_name = (struct project_display *)((u8 *)fw->project_names +
// cpu_to_be32(fw->pdisplay_list->row_size) * i);
//
// if(!memcmp(pproject_name->project_name,
// CS48L10_BOOT_ASSIST_PROJECT_NAME,
// strlen(CS48L10_BOOT_ASSIST_PROJECT_NAME))) {
// cs48l10->boot_assist = true;
// cs48l10->boot_assist_project = i;
// break;
// }
// }
// if(cs48l10->boot_assist_project == CS48L10_DEFAULT_PROJECT_ID) {
// dev_err(cs48l10->dev,
// "Boot assist project can't be the same as the default.\n");
// return -EINVAL;
// }
//
// mutex_lock(&cs48l10->lock);
//
// if(cs48l10->boot_assist == true) {
// ret = cs48l10_fw_bootassist_project(cs48l10,
// CS48L10_DEFAULT_PROJECT_ID, INITIAL_CFG);
// if(ret < 0)
// dev_err(cs48l10->dev,
// "Boot assist project failed to start 0x%x\n", ret);
//
// } else {
// /* Boot the default project */
// ret = cs48l10_fw_boot_project(cs48l10,
// CS48L10_DEFAULT_PROJECT_ID, INITIAL_CFG);
// if (ret < 0)
// dev_err(cs48l10->dev,
// "Project %d failed to start, ret = %d\n",
// CS48L10_DEFAULT_PROJECT_ID, ret);
// }
//
// mutex_unlock(&cs48l10->lock);
// return 0;
//}
/* Microcondenser Firmware Loader */
static void cs48l10_fw_load_async_cont(const struct firmware *osfw,
void *context)
{
struct cs48l10 *cs48l10 = (struct cs48l10 *)context;
int ret;
int i;
struct cs48l10_firmware *fw;
struct project_display *pproject_name;
dev_dbg(cs48l10->dev, "%s ...\n", __func__);
if (!cs48l10)
return;
if (!osfw) {
dev_err(cs48l10->dev, "Firmware not available\n");
return;
}
if (cs48l10->fw)
kfree(cs48l10->fw);
if (cs48l10->osfw)
release_firmware(cs48l10->osfw);
cs48l10->osfw = osfw;
cs48l10->fw = (struct cs48l10_firmware *)
kzalloc(sizeof(struct cs48l10_firmware), GFP_KERNEL);
if (!cs48l10->fw) {
release_firmware(cs48l10->osfw);
cs48l10->osfw = NULL;
mutex_unlock(&cs48l10->lock);
return;
}
ret = cs48l10_fw_parse_firmware(cs48l10);
if (ret < 0) {
dev_err(cs48l10->dev,
"firmware (%u bytes) failed to load, ret = %d\n",
cs48l10->osfw->size, ret);
kfree(cs48l10->fw);
release_firmware(cs48l10->osfw);
cs48l10->osfw = NULL;
cs48l10->fw = NULL;
return;
}
dev_dbg(cs48l10->dev,
"firmware (%u bytes) parsed successfully\n",
cs48l10->osfw->size);
fw = cs48l10->fw;
/*Set boot_assist_project to < 0 to identify that it was not set. */
cs48l10->boot_assist_project = -1;
cs48l10->boot_assist = false;
for(i = 0; i < cpu_to_be32(fw->pmaster_list->num_projects); i++) {
pproject_name = (struct project_display *)((u8 *)fw->project_names +
cpu_to_be32(fw->pdisplay_list->row_size) * i);
if(!memcmp(pproject_name->project_name,
CS48L10_BOOT_ASSIST_PROJECT_NAME,
strlen(CS48L10_BOOT_ASSIST_PROJECT_NAME))) {
cs48l10->boot_assist = true;
cs48l10->boot_assist_project = i;
break;
}
}
if(cs48l10->boot_assist_project == CS48L10_DEFAULT_PROJECT_ID) {
dev_err(cs48l10->dev,
"Boot assist project can't be the same as the default.\n");
return;
}
mutex_lock(&cs48l10->lock);
if(cs48l10->boot_assist == true) {
ret = cs48l10_fw_bootassist_project(cs48l10,
CS48L10_DEFAULT_PROJECT_ID, INITIAL_CFG);
if(ret < 0)
dev_err(cs48l10->dev,
"Boot assist project failed to start 0x%x\n", ret);
} else {
/* Boot the default project */
ret = cs48l10_fw_boot_project(cs48l10,
CS48L10_DEFAULT_PROJECT_ID, INITIAL_CFG);
if (ret < 0)
dev_err(cs48l10->dev,
"Project %d failed to start, ret = %d\n",
CS48L10_DEFAULT_PROJECT_ID, ret);
}
mutex_unlock(&cs48l10->lock);
cs48l10->fw_loaded = true;
dev_info(cs48l10->dev, "Firmware loading completed\n");
// Cirrus to sleep
for (i=0; i<ARRAY_SIZE(Sleep_cmd); i++)
cs48l10->write_word(cs48l10, Sleep_cmd[i]);
cs48l10->sleep_mode = 1;
// disable clock
usleep_range(2000, 2000);
clk_disable_unprepare(cs48l10->clk);
}
/* Asynchronous load from a microcondenser image from /lib/firmware */
static int cs48l10_fw_load_async(struct cs48l10 *cs48l10, const char *filename)
{
int ret;
cs48l10->fw = NULL;
cs48l10->osfw = NULL;
dev_dbg(cs48l10->dev, "%s ...\n", __func__);
ret =
request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, filename,
cs48l10->dev, GFP_KERNEL, cs48l10,
cs48l10_fw_load_async_cont);
if (ret != 0) {
dev_err(cs48l10->dev,
"request async load of %s failed. err = %d\n",
filename,
ret);
return ret;
}
return 0;
}
static ssize_t cs48l10_hibernate_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", cs48l10->hibernate);
}
/* Shows the firmware version number via the sysfs */
static ssize_t cs48l10_fw_ver_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
if(strlen(cs48l10->firmware_version))
return sprintf(buf, "%s\n", cs48l10->firmware_version);
else
return -EINVAL;
}
static ssize_t cs48l10_fw_ver_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return 0;
}
static bool is_msg_list_empty(struct cs48l10 *cs48l10) {
bool empty;
mutex_lock(&cs48l10->lock);
empty = list_empty(&cs48l10->unsol_list);
mutex_unlock(&cs48l10->lock);
return empty;
}
static int err_num;
static ssize_t cs48l10_dsp_msg_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int len = 0;
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
struct unsol_message *umsg, *n;
dev_dbg(cs48l10->dev,"%s: %s\n", __func__, list_empty(&cs48l10->unsol_list) ? "none yet":"got some");
while (is_msg_list_empty(cs48l10)) {
if (msleep_interruptible(100)) {
len = scnprintf(buf, PAGE_SIZE, "none\n");
dev_dbg(cs48l10->dev,"%s interrupted\n", __func__);
return len;
}
}
mutex_lock(&cs48l10->lock);
list_for_each_entry_safe(umsg, n, &cs48l10->unsol_list, link) {
switch(umsg->data[0]){
case CS48L10_MALLOC_ERR:
len += scnprintf(buf + len, PAGE_SIZE - len, "id=%d MALLOC ERROR: 0x%08x%08x\n", err_num++, umsg->data[0], umsg->data[1]);
break;
case CS48L10_PLL_ERR:
len += scnprintf(buf + len, PAGE_SIZE - len, "id=%d PLL ERROR: 0x%08x%08x\n", err_num++, umsg->data[0], umsg->data[1]);
break;
default:
len += scnprintf(buf + len, PAGE_SIZE - len, "[%u] %08x %08x\n", umsg->timestamp_us, umsg->data[0], umsg->data[1]);
break;
}
list_del(&umsg->link);
kfree(umsg);
cs48l10->unsol_length--;
if (len == PAGE_SIZE)
break;
}
mutex_unlock(&cs48l10->lock);
return len;
}
static ssize_t cs48l10_dsp_msg_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
struct unsol_message *umsg;//, *n;
mutex_lock(&cs48l10->lock);
umsg = kzalloc(sizeof(struct unsol_message),
GFP_KERNEL);
if (umsg == NULL) {
dev_err(cs48l10->dev,
"Cannot allocate memory in %s\n", __FUNCTION__);
return -ENOMEM;
}
umsg->timestamp_us = jiffies_to_msecs(jiffies);
sscanf(buf, "%x %x", &umsg->data[0], &umsg->data[1]);
list_add_tail(&umsg->link, &cs48l10->unsol_list);
cs48l10->unsol_length++;
mutex_unlock(&cs48l10->lock);
return count;
}
static ssize_t cs48l10_hibernate_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
u32 Hibernate_Mode[2] = { 0x81000009, 0x00000011 };
int ret = 0, i;
mutex_lock(&cs48l10->lock);
/* Send the Hibernate Mode command to the DSP */
for (i = 0; i < ARRAY_SIZE(Hibernate_Mode); i++)
{
if ((ret = cs48l10->write_word(cs48l10,
Hibernate_Mode[i])) < 0)
break;
}
cs48l10->hibernate = 1;
if(ret < 0)
{
mutex_unlock(&cs48l10->lock);
return ret;
}
else
{
mutex_unlock(&cs48l10->lock);
return count;
}
}
/* GPIO */
static int cs48l10_setup_int_gpio(struct cs48l10 *cs48l10)
{
int ret;
if (!cs48l10)
return -EINVAL;
ret = gpio_request(cs48l10->pdata.gpio_int, "CS48L10_nINT");
if (ret)
return -EINVAL;
ret = gpio_direction_input(cs48l10->pdata.gpio_int);
if (ret) {
gpio_free(cs48l10->pdata.gpio_int);
return ret;
}
cs48l10->irq = gpio_to_irq(cs48l10->pdata.gpio_int);
ret = request_irq(cs48l10->irq,
cs48l10_int_handler,
IRQF_TRIGGER_FALLING,
"cs48l10_int",
cs48l10);
if (ret) {
gpio_free(cs48l10->pdata.gpio_int);
return ret;
}
/* Setup wake on interrupt */
ret = enable_irq_wake(cs48l10->irq);
if (ret == 0) {
ret = device_init_wakeup(cs48l10->dev, 1);
if (ret) {
dev_err(cs48l10->dev, "Failed to init device"
"wakeup : %d\n", ret);
disable_irq_wake(cs48l10->irq);
}
} else
dev_err(cs48l10->dev, "Failed to set wake interrupt on"
" IRQ %d: %d\n", cs48l10->irq, ret);
#ifdef CONFIG_GPIO_SYSFS
/* Expose GPIO value over sysfs for diagnostic purposes */
gpio_export(cs48l10->pdata.gpio_int, false);
#endif
dev_dbg(cs48l10->dev, "CS48L10_nINT gpio %d -> irq %d\n",
cs48l10->pdata.gpio_int, gpio_to_irq(cs48l10->pdata.gpio_int));
return 0;
}
static int cs48l10_setup_reset_gpio(struct cs48l10 *cs48l10)
{
int ret;
if (!cs48l10)
return -EINVAL;
ret = gpio_request(cs48l10->pdata.gpio_nreset, "CS48L10_nRESET");
if (ret)
return -EINVAL;
ret = gpio_direction_output(cs48l10->pdata.gpio_nreset, 0);
if (ret) {
gpio_free(cs48l10->pdata.gpio_nreset);
return ret;
}
#ifdef CONFIG_GPIO_SYSFS
/* Expose GPIO value over sysfs for diagnostic purposes */
gpio_export(cs48l10->pdata.gpio_nreset, false);
#endif
dev_dbg(cs48l10->dev, "CS48L10_nRESET gpio %d\n", cs48l10->pdata.gpio_nreset);
return 0;
}
#ifndef CS48L10_NOBUSY
static int cs48l10_setup_busy_gpio(struct cs48l10 *cs48l10)
{
int ret;
if (!cs48l10)
return -EINVAL;
if (!gpio_is_valid(cs48l10->pdata.gpio_busy)) {
dev_err(cs48l10->dev, "%s invalid gpio %d\n", __func__, cs48l10->pdata.gpio_busy);
return -EINVAL;
}
ret = gpio_request(cs48l10->pdata.gpio_busy, "CS48L10_nBUSY");
if (ret) {
dev_err(cs48l10->dev, "%s request for gpio %d failed with %d\n", __func__, cs48l10->pdata.gpio_busy, ret);
return -EINVAL;
}
ret = gpio_direction_input(cs48l10->pdata.gpio_busy);
if (ret) {
dev_err(cs48l10->dev, "%s request for gpio %d direction failed with %d\n", __func__, cs48l10->pdata.gpio_busy, ret);
gpio_free(cs48l10->pdata.gpio_busy);
return ret;
}
#ifdef CONFIG_GPIO_SYSFS
/* Expose GPIO value over sysfs for diagnostic purposes */
gpio_export(cs48l10->pdata.gpio_busy, false);
#endif
dev_dbg(cs48l10->dev, "CS48L10_nBUSY gpio %d\n",
cs48l10->pdata.gpio_busy);
return 0;
}
#endif //CS48L10_NOBUSY
static int cs48l10_free_gpios(struct cs48l10* cs48l10)
{
if (cs48l10->pdata.gpio_int) {
free_irq(gpio_to_irq(cs48l10->pdata.gpio_int), cs48l10);
gpio_free(cs48l10->pdata.gpio_int);
}
if (cs48l10->pdata.gpio_nreset)
gpio_free(cs48l10->pdata.gpio_nreset);
#ifndef CS48L10_NOBUSY
if (cs48l10->pdata.gpio_busy)
gpio_free(cs48l10->pdata.gpio_busy);
#endif
return 0;
}
static int cs48l10_setup_gpios(struct cs48l10 *cs48l10)
{
dev_dbg(cs48l10->dev,"%s int %d rst %d bsy %d\n", __func__,
cs48l10->pdata.gpio_int, cs48l10->pdata.gpio_nreset, cs48l10->pdata.gpio_busy);
cs48l10_setup_reset_gpio(cs48l10);
cs48l10_setup_int_gpio(cs48l10);
#ifndef CS48L10_NOBUSY
cs48l10_setup_busy_gpio(cs48l10);
#endif
return 0;
}
/* sysfs entries */
static ssize_t cs48l10_project_id_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
struct cs48l10_firmware *fw = cs48l10->fw;
if (!fw || !fw->pmaster_list) {
dev_err(cs48l10->dev, "Firmware is not loaded\n");
return -EINVAL;
}
return sprintf(buf, "%u\n", fw->active_project);
}
static ssize_t cs48l10_reset_dsp_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
int res = cs48l10_dsp_reset(cs48l10);
if (res < 0)
return res;
return count;
}
static ssize_t cs48l10_reset_dsp_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return -1;
}
static ssize_t cs48l10_sleep_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
struct cs48l10_firmware *fw = cs48l10->fw;
u32 dsp_result;
unsigned long is_sleep_mode = 0;
int ret, i;
if (!fw || !fw->pmaster_list) {
dev_err(cs48l10->dev, "Firmware is not loaded\n");
return -EINVAL;
}
ret = strict_strtoul(buf, 10, &is_sleep_mode);
if (ret < 0)
return ret;
if(cs48l10->hibernate)
return count;
if (is_sleep_mode) {
if (cs48l10->sleep_mode == 0) {
for (i=0; i<ARRAY_SIZE(Sleep_cmd); i++)
cs48l10->write_word(cs48l10, Sleep_cmd[i]);
cs48l10->sleep_mode = 1;
}
} else {
if (cs48l10->sleep_mode == 1) {
cs48l10_write_then_read(cs48l10,
Wakeup_cmd, ARRAY_SIZE(Wakeup_cmd),
&dsp_result, 1);
cs48l10->sleep_mode = 0;
}
}
return count;
}
static ssize_t cs48l10_sleep_mode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", cs48l10->sleep_mode);
}
static ssize_t cs48l10_project_id_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
struct cs48l10_firmware *fw = cs48l10->fw;
unsigned long project_id = 0;
int ret;
if (!fw || !fw->pmaster_list)
return -EINVAL;
ret = strict_strtoul(buf, 10, &project_id);
if (ret)
return ret;
if (project_id >= cpu_to_be32(fw->pmaster_list->num_projects)) {
dev_err(cs48l10->dev,
"Invalid Project id = %d\n",
(int)project_id);
return -EINVAL;
}
mutex_lock(&cs48l10->lock);
if(cs48l10->boot_assist_project >= 0) {
cs48l10->boot_assist = true;
ret = cs48l10_fw_bootassist_project(cs48l10,
project_id, INITIAL_CFG);
if(ret < 0)
dev_err(cs48l10->dev,
"Boot assist project failed to start 0x%x\n", ret);
} else {
if (cs48l10->reset)
ret = cs48l10_fw_boot_project(cs48l10, project_id, INITIAL_CFG);
else
ret = cs48l10_fw_softboot_project(cs48l10, project_id, INITIAL_CFG);
if (ret < 0)
dev_err(cs48l10->dev,
"Project %ld failed to start, ret = %d\n",
project_id, ret);
}
mutex_unlock(&cs48l10->lock);
if (ret < 0)
return ret;
return count;
}
static ssize_t cs48l10_snapshot_id_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
struct cs48l10_firmware *fw = cs48l10->fw;
if (!fw || !fw->pmaster_list) {
dev_err(cs48l10->dev, "Firmware is not loaded\n");
return -EINVAL;
}
return sprintf(buf, "%u\n", fw->current_snapshot);
}
static ssize_t cs48l10_snapshot_id_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
struct cs48l10_firmware *fw = cs48l10->fw;
unsigned long snapshot_id = 0;
int ret=0, wake_count;
bool no_init_snapshot = false;
char snapshot_name[MAX_DISPLAY_LENGTH];
u32 dsp_result;
memset(&snapshot_name[0], 0, MAX_DISPLAY_LENGTH);
if (!fw || !fw->pmaster_list) {
dev_err(cs48l10->dev, "Firmware is not loaded\n");
return -EINVAL;
}
ret = strict_strtoul(buf, 10, &snapshot_id);
if (ret < 0)
return ret;
if (snapshot_id >= cpu_to_be32(fw->pmaster_list->cfgs_per_project)
|| snapshot_id < INITIAL_CFG) {
dev_err(cs48l10->dev,
"Invalid snapshot_id %lu\n", snapshot_id);
return count;
}
mutex_lock(&cs48l10->lock);
cmp_micro_condenser_get_snapshot_name
(
cs48l10,
fw->active_project,
snapshot_id,
snapshot_name
);
if(!strncmp(CS48L10_SLEEP_SNAPSHOT_NAME, snapshot_name,
strlen(CS48L10_SLEEP_SNAPSHOT_NAME))){
no_init_snapshot = true;
} else if(!strncmp(CS48L10_WAKE_SNAPSHOT_NAME, snapshot_name,
strlen(CS48L10_WAKE_SNAPSHOT_NAME))) {
no_init_snapshot = true;
for(wake_count = 0; wake_count < 3; wake_count++) {
cs48l10_write_then_read(cs48l10,
Wakeup_cmd, ARRAY_SIZE(Wakeup_cmd),
&dsp_result, 1);
if(dsp_result == CS48L10_APP_START)
break;
}
if(wake_count == 3){
dev_err(cs48l10->dev,
"Failed to set wake up command\n");
return count;
}
} else {
no_init_snapshot = false;
}
/* Check to see if the "wake" or "sleep" snapshots are being set.
* If not, load the INITIAL_CFG snapshot first.
*/
if (snapshot_id != INITIAL_CFG && no_init_snapshot == false)
ret = cs48l10_fw_load_config(cs48l10, INITIAL_CFG);
if (!ret)
ret = cs48l10_fw_load_config(cs48l10, snapshot_id);
mutex_unlock(&cs48l10->lock);
if (ret < 0) {
dev_err(cs48l10->dev,
"Snapshot %lu failed to load, ret = %d\n",
snapshot_id, ret);
fw->current_snapshot = -1;
return ret;
}
fw->current_snapshot = snapshot_id;
return count;
}
static ssize_t cs48l10_snapshot_name_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return 0;
}
static ssize_t cs48l10_snapshot_name_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
struct cs48l10_firmware *fw = cs48l10->fw;
if (!fw || !fw->pmaster_list) {
dev_err(cs48l10->dev, "Firmware is not loaded\n");
return -EINVAL;
}
cmp_micro_condenser_get_snapshot_name
(
cs48l10,
fw->active_project,
fw->current_snapshot,
buf
);
return strlen(buf);
}
static ssize_t cs48l10_dsp_info_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return -EPERM;
}
static ssize_t cs48l10_dsp_info_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
struct cs48l10_firmware *fw = cs48l10->fw;
struct project_display_list * display_info;
struct project_display * project_info;
int flash_addr;
int prj_num, max_snp_num, row_size;
int i, j, len = 0;
dev_info(cs48l10->dev, "%s\n", __func__);
if (!fw || !fw->pmaster_list) {
dev_err(cs48l10->dev, "Firmware is not loaded\n");
return -EINVAL;
}
if (fw->pflash_image->project_display_ptr) {
flash_addr =
be32_to_cpu(fw->pflash_image->project_display_ptr);
if(cs48l10_fw_validate_ptr(fw, flash_addr )) {
dev_err(dev, "flash_addr check failed\n");
return -EIO;
}
display_info = (struct project_display_list *)
(((u8 *)cs48l10->fw->firmware_data) + flash_addr);
prj_num = be32_to_cpu(display_info->num_projects);
max_snp_num = be32_to_cpu(display_info->max_snapshot_names);
row_size = be32_to_cpu(display_info->row_size);
dev_info(cs48l10->dev, "%s prj.num %d row.sz %d max.snp.num %d\n", __func__, prj_num, row_size, max_snp_num);
for (i = 0; i < prj_num; i++) {
project_info = (struct project_display *)
(( u8 *) &display_info->display + (row_size * i));
len += scnprintf(&buf[len], PAGE_SIZE - len, "%1.15s:2=Initial,", project_info->project_name);
dev_info(cs48l10->dev, "%s buf %s\n", __func__, buf);
for (j = 0; (len < PAGE_SIZE - 1) && j < max_snp_num; j++) {
if (project_info->snapshot_name[j][0] != 0) {
dev_info(cs48l10->dev, "%s \tsn %s,\n", __func__, &project_info->snapshot_name[j][0]);
len += scnprintf(&buf[len], PAGE_SIZE - len, "%d=%1.15s,", j + 3, &project_info->snapshot_name[j][0]);
} else {
break;
}
}
if (len < PAGE_SIZE - 1) {
len += scnprintf(&buf[len], PAGE_SIZE - len, ";");
} else {
return -ENOMEM;
}
}
} else {
return -EINVAL;
}
return len;
}
static ssize_t cs48l10_project_name_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return 0;
}
static ssize_t cs48l10_project_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
struct cs48l10_firmware *fw = cs48l10->fw;
//
// Get the project name.
//
cmp_micro_condenser_get_project_name
(
cs48l10,
fw->current_project,
buf
);
/* Return the last snapshot that was used */
return strlen(buf);
}
static ssize_t cs48l10_urd(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
dev_dbg(cs48l10->dev,"%s %08x %08x\n", __func__, cs48l10->urd[0], cs48l10->urd[1]);
return scnprintf(buf, PAGE_SIZE, "%08x %08x", cs48l10->urd[0], cs48l10->urd[1]);
}
static ssize_t cs48l10_ucmd(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(dev);
int i, j, ret;
u32 buffer[256];
u32 oc=0;
u8 c=0;
memset(buffer, 0, sizeof(buffer));
dev_dbg(cs48l10->dev,"%s %1.*s\n", __func__, count, buf);
j = 0;
for (i = 0; i< count ; i++) {
c = buf[i];
if(c == ' ' || c == '\n')
continue;
if(c >='0' && c <='9')
oc |= c - '0';
else if (c >='A' && c <='F')
oc |= c - 'A' + 10;
else if (c >='a' && c <='f')
oc |= c -'a' + 10;
else
return -EINVAL;
if(j >256)
return -EINVAL;
j++;
if ((j % 8) == 0) {
buffer[j/8-1] = oc;
oc = 0;
} else {
oc <<= 4;
}
}
if (j%8 != 0)
return -EINVAL;
mutex_lock(&cs48l10->lock);
if ((buffer[0] & CS48l10_RW_CMD_MASK) == CS48l10_RW_CMD_MASK) {
//read command - response is expected
cs48l10->urd_cnt = cs48l10_write_then_read(cs48l10, buffer, j/8, cs48l10->urd, 2);
if (cs48l10->urd_cnt < 0)
count = cs48l10->urd_cnt;
} else {
for (i = 0; i < j/8; i++) {
if ((ret = cs48l10->write_word(cs48l10, buffer[i])) < 0) {
count = ret;
break;
}
}
}
mutex_unlock(&cs48l10->lock);
return count;
}
static DEVICE_ATTR(ucmd_urd, 0666, cs48l10_urd, cs48l10_ucmd);
static DEVICE_ATTR(snapshot_name, 0666, cs48l10_snapshot_name_show,
cs48l10_snapshot_name_store);
static DEVICE_ATTR(project_name, 0666, cs48l10_project_name_show,
cs48l10_project_name_store);
static DEVICE_ATTR(snapshot_id, 0666,
cs48l10_snapshot_id_show, cs48l10_snapshot_id_store);
static DEVICE_ATTR(project_id, 0666,
cs48l10_project_id_show, cs48l10_project_id_store);
static DEVICE_ATTR(hibernate_mode, 0666, cs48l10_hibernate_mode_show,
cs48l10_hibernate_mode_store);
static DEVICE_ATTR(dsp_reset, 0666, cs48l10_reset_dsp_show,
cs48l10_reset_dsp_store);
static DEVICE_ATTR(sleep_mode, 0666, cs48l10_sleep_mode_show,
cs48l10_sleep_mode_store);
static DEVICE_ATTR(fw_version, 0666, cs48l10_fw_ver_show,
cs48l10_fw_ver_store);
static DEVICE_ATTR(dsp_msg, 0666, cs48l10_dsp_msg_show,
cs48l10_dsp_msg_store);
static DEVICE_ATTR(dsp_info, 0666, cs48l10_dsp_info_show,
cs48l10_dsp_info_store);
static struct attribute *cs48l10_attributes[] = {
&dev_attr_project_id.attr,
&dev_attr_snapshot_id.attr,
&dev_attr_project_name.attr,
&dev_attr_snapshot_name.attr,
&dev_attr_ucmd_urd.attr,
&dev_attr_hibernate_mode.attr,
&dev_attr_dsp_reset.attr,
&dev_attr_sleep_mode.attr,
&dev_attr_fw_version.attr,
&dev_attr_dsp_msg.attr,
&dev_attr_dsp_info.attr,
NULL
};
static const struct attribute_group cs48l10_attr_group = {
.attrs = cs48l10_attributes,
};
/* END sysfs */
static int cs48l10_dsp_pwr_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = w->codec;
struct cs48l10 *cs48l10 = snd_soc_codec_get_drvdata(codec);
u32 dsp_result;
int i;
dev_dbg(codec->dev, "%s wname %s evt %d active %d\n", __func__,
w->name, event, cs48l10->active);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
if (cs48l10->sleep_mode == 1) {
clk_prepare_enable(cs48l10->clk);
cs48l10_write_then_read(cs48l10,
Wakeup_cmd, ARRAY_SIZE(Wakeup_cmd),
&dsp_result, 1);
if(dsp_result != CS48L10_APP_START) {
dev_err(codec->dev, "%s failed to wake up device\n", __func__);
//return -EIO;
}
cs48l10->sleep_mode = 0;
}
cs48l10->active++;
break;
case SND_SOC_DAPM_POST_PMD:
cs48l10->active--;
if (cs48l10->sleep_mode == 0 && !cs48l10->active) {
for (i=0; i<ARRAY_SIZE(Sleep_cmd); i++)
cs48l10->write_word(cs48l10, Sleep_cmd[i]);
cs48l10->sleep_mode = 1;
usleep_range(2000, 2000);
clk_disable_unprepare(cs48l10->clk);
}
break;
default:
dev_err(codec->dev, "%s Invalid event = 0x%x\n", __func__, event);
}
return 0;
}
static const struct snd_soc_dapm_widget cs48l10_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN_E("DAI0", "DAI0 Playback", 0, SND_SOC_NOPM, 0, 0,
cs48l10_dsp_pwr_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_AIF_IN_E("DAI1", "DAI1 Playback", 0, SND_SOC_NOPM, 0, 0,
cs48l10_dsp_pwr_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_AIF_OUT_E("DAO0", "DAO0 Capture", 0, SND_SOC_NOPM, 0, 0,
cs48l10_dsp_pwr_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_AIF_OUT_E("DAO1", "DAO1 Capture", 0, SND_SOC_NOPM, 0, 0,
cs48l10_dsp_pwr_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
//DSP as auxiliary codec PM controls
//(should be added to DAPM routes of the main codec in audio machine driver)
SND_SOC_DAPM_MIXER_E("DAO_Mixer", SND_SOC_NOPM, 0, 0, NULL, 0,
cs48l10_dsp_pwr_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_MIXER_E("DAI_Mixer", SND_SOC_NOPM, 0, 0, NULL, 0,
cs48l10_dsp_pwr_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
};
static const struct snd_soc_dapm_route cs48l10_dapm_routes[] = {
{"DAO0", NULL, "DAI0"},
{"DAO1", NULL, "DAI0"},
{"DAO0", NULL, "DAI1"},
{"DAO1", NULL, "DAI1"},
};
static int cs48l10_probe(struct snd_soc_codec *codec)
{
dev_dbg(codec->dev, "%s codec %s\n", __func__, codec->name);
return 0;
}
static struct snd_soc_codec_driver soc_codec_cs48l10 = {
.probe = cs48l10_probe,
.dapm_widgets = cs48l10_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(cs48l10_dapm_widgets),
.dapm_routes = cs48l10_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(cs48l10_dapm_routes),
};
#define CS48L10_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
#define CS48L10_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE |\
SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver cs48l10_dai[] = {
{
.name = "cs48l10-dai0",
.playback = {
.stream_name = "DAI0 Playback",
.channels_min = 1,
.channels_max = 8,
.rates = CS48L10_RATES,
.formats = CS48L10_FORMATS,
},
},
{
.name = "cs48l10-dao0",
.capture = {
.stream_name = "DAO0 Capture",
.channels_min = 1,
.channels_max = 8,
.rates = CS48L10_RATES,
.formats = CS48L10_FORMATS,
},
},
{
.name = "cs48l10-dai1",
.playback = {
.stream_name = "DAI1 Playback",
.channels_min = 1,
.channels_max = 8,
.rates = CS48L10_RATES,
.formats = CS48L10_FORMATS,
},
},
{
.name = "cs48l10-dao1",
.capture = {
.stream_name = "DAO1 Capture",
.channels_min = 1,
.channels_max = 8,
.rates = CS48L10_RATES,
.formats = CS48L10_FORMATS,
},
},
};
static struct regmap_config cs48l10_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
};
static const struct of_device_id cs48l10_of_match[] = {
{ .compatible = "crus,cs48l10", },
{},
};
MODULE_DEVICE_TABLE(of, cs48l10_of_match);
static void cs48l10_set_reset_state(void *crus_dsp, int state) {
struct cs48l10 *cs48l10 = crus_dsp;
int ret;
dev_dbg(cs48l10->dev,"%s %x\n", __func__, state);
gpio_set_value(cs48l10->pdata.gpio_nreset, state);
if (state)
cs48l10->sol_count = 1;
if (cs48l10->spi) {
if (state) {
ret = gpio_direction_input(cs48l10->pdata.gpio_busy);
if (ret) {
dev_err(cs48l10->dev, "%s request for gpio %d direction failed with %d\n", __func__, cs48l10->pdata.gpio_busy, ret);
}
ret = gpio_direction_input(cs48l10->pdata.gpio_int);
if (ret) {
dev_err(cs48l10->dev, "%s request for gpio %d direction failed with %d\n", __func__, cs48l10->pdata.gpio_busy, ret);
}
} else {
ret = gpio_direction_output(cs48l10->pdata.gpio_busy, 1);
if (ret) {
dev_err(cs48l10->dev, "%s request for gpio %d direction failed with %d\n", __func__, cs48l10->pdata.gpio_busy, ret);
}
ret = gpio_direction_output(cs48l10->pdata.gpio_int, 1);
if (ret) {
dev_err(cs48l10->dev, "%s request for gpio %d direction failed with %d\n", __func__, cs48l10->pdata.gpio_busy, ret);
}
}
}
}
static int cs48l10_get_reset_state(void *crus_dsp) {
struct cs48l10 *cs48l10 = crus_dsp;
return gpio_get_value(cs48l10->pdata.gpio_nreset);
}
static int cs48l10_get_busy_state(void *crus_dsp) {
struct cs48l10 *cs48l10 = crus_dsp;
return gpio_get_value(cs48l10->pdata.gpio_busy);
}
static int cs48l10_get_int_state(void *crus_dsp) {
struct cs48l10 *cs48l10 = crus_dsp;
return gpio_get_value(cs48l10->pdata.gpio_int);
}
static int dspctl_write_data(void *crus_dsp, u32* txwords, int txlen) {
struct cs48l10 *cs48l10 = crus_dsp;
int ret, i;
mutex_lock(&cs48l10->lock);
cs48l10->sol_count = 1;
for (i = 0; i < txlen; i++) {
ret = cs48l10->ctl_write_word(cs48l10, txwords[i]);
if(ret < 0) {
mutex_unlock(&cs48l10->lock);
return ret;
}
}
mutex_unlock(&cs48l10->lock);
return txlen;
}
static int dspctl_read_resp(void *crus_dsp, u32* rxwords, int rxlen) {
struct cs48l10 *cs48l10 = crus_dsp;
int ret, i = 0;
mutex_lock(&cs48l10->lock);
if (gpio_get_value(cs48l10->pdata.gpio_int)) {
/* Wait for nINT */
ret = wait_event_interruptible_timeout(cs48l10->rx_data_wait,
!gpio_get_value(cs48l10->pdata.gpio_int), 1 * HZ);
if (ret < 0) {
dev_err(cs48l10->dev,
"%s(): Waiting for DSP response failed with err %d\n",
__func__, ret);
goto ext;
}
if (!ret) {
dev_err(cs48l10->dev,
"%s: Timeout while waiting for DSP response\n",
__func__);
goto ext;
}
}
for (i = 0; i < rxlen && !gpio_get_value(cs48l10->pdata.gpio_int); i++) {
ret = cs48l10->ctl_read_word(cs48l10, &rxwords[i]);
if(ret < 0) {
break;
}
}
ext:
cs48l10->sol_count = 0;
mutex_unlock(&cs48l10->lock);
return i;
}
static int dspctl_open(void *crus_dsp) {
struct cs48l10 *cs48l10 = crus_dsp;
dev_dbg(cs48l10->dev,"%s\n", __func__);
if (!cs48l10->ctl_read_word) {
disable_irq(cs48l10->irq);
mutex_lock(&cs48l10->lock);
cs48l10->ctl_read_word = cs48l10->read_word;
cs48l10->ctl_write_word = cs48l10->write_word;
cs48l10->ctl_write_word_buf = cs48l10->write_word_buf;
cs48l10->read_word = cs48l10_noop_read_word;
cs48l10->write_word = cs48l10_noop_write_word;
cs48l10->write_word_buf = cs48l10_noop_write_buf;
mutex_unlock(&cs48l10->lock);
} else {
dev_err(cs48l10->dev,"%s already opened\n", __func__);
return -EBUSY;
}
return 0;
}
static int dspctl_close(void *crus_dsp) {
struct cs48l10 *cs48l10 = crus_dsp;
dev_dbg(cs48l10->dev,"%s\n", __func__);
if (cs48l10->ctl_read_word) {
mutex_lock(&cs48l10->lock);
cs48l10->read_word = cs48l10->ctl_read_word;
cs48l10->write_word = cs48l10->ctl_write_word;
cs48l10->write_word_buf = cs48l10->ctl_write_word_buf;
cs48l10->ctl_read_word = NULL;
cs48l10->ctl_write_word = NULL;
cs48l10->ctl_write_word_buf = NULL;
mutex_unlock(&cs48l10->lock);
enable_irq(cs48l10->irq);
} else {
dev_err(cs48l10->dev,"%s already closed\n", __func__);
return -ENODEV;
}
return 0;
}
static struct dsp_if cs48l10_dsp_if = {
.dsp_open = dspctl_open,
.dsp_close = dspctl_close,
.dsp_write_data = dspctl_write_data,
.dsp_read_resp = dspctl_read_resp,
.dsp_set_reset_state = cs48l10_set_reset_state,
.dsp_get_reset_state = cs48l10_get_reset_state,
.dsp_get_busy_state = cs48l10_get_busy_state,
.dsp_get_int_state = cs48l10_get_int_state,
};
struct dsp_if *crus_get_dsp_if(void) {
return &cs48l10_dsp_if;
}
EXPORT_SYMBOL_GPL(crus_get_dsp_if);
static int cs48l10_spi_probe(struct spi_device *spi)
{
struct cs48l10 *cs48l10;
const struct device_node *np = spi->dev.of_node;
struct cs48l10_platform_data *pdata = dev_get_platdata(&spi->dev);
int i;
int ret = 0;
u32 val = 0;
dev_info(&spi->dev, "%s %s on %s\n", __func__, dev_name(&spi->dev), dev_name(&(spi->master->dev)));
cs48l10 = kzalloc(sizeof(struct cs48l10), GFP_KERNEL);
if (cs48l10 == NULL)
return -ENOMEM;
cs48l10->spi = spi; /* cs48l10 to spi reference */
cs48l10->dev = &spi->dev;
dev_set_drvdata(&spi->dev, cs48l10); /* spi to cs48l10 reference */
cs48l10->read_word = cs48l10_read_dsp_word;
cs48l10->write_word = cs48l10_write_dsp_word;
cs48l10->write_word_buf = cs48l10_write_buf;
cs48l10->regmap = regmap_init_spi(spi, &cs48l10_regmap_config);
if (IS_ERR(cs48l10->regmap)) {
ret = PTR_ERR(cs48l10->regmap);
dev_err(cs48l10->dev, "Failed to allocate register map: %d\n",
ret);
goto err;
}
mutex_init(&cs48l10->lock);
init_waitqueue_head(&cs48l10->rx_data_wait);
INIT_LIST_HEAD(&cs48l10->unsol_list);
INIT_WORK(&cs48l10->unsol_work, unsol_dsp_mess);
cs48l10->sol_spinlock = __SPIN_LOCK_UNLOCKED(cs48l10->sol_spinlock);
if (pdata) {
cs48l10->pdata.gpio_nreset = pdata->gpio_nreset;
cs48l10->pdata.gpio_int = pdata->gpio_int;
cs48l10->pdata.gpio_busy = pdata->gpio_busy;
} else {
if (of_property_read_u32(np, "nreset-gpios", &val) >= 0)
cs48l10->pdata.gpio_nreset = val;
if (of_property_read_u32(np, "int-gpios", &val) >= 0)
cs48l10->pdata.gpio_int = val;
if (of_property_read_u32(np, "gpio_busy", &val) >= 0)
cs48l10->pdata.gpio_busy = val;
}
/* Get and enable external clock */
cs48l10->clk = devm_clk_get(cs48l10->dev, NULL);
if (IS_ERR(cs48l10->clk)) {
dev_err(cs48l10->dev, "couldn't get clock\n");
ret = -ENODEV;
goto err;
}
clk_prepare_enable(cs48l10->clk);
/* setup platform RESET/INT/BUSY functions */
if ((ret = cs48l10_setup_gpios(cs48l10)) < 0) {
dev_err(cs48l10->dev, "Error: Can't set gpios. ret = %d\n", ret);
goto err;
}
if ((ret = sysfs_create_group(&spi->dev.kobj,
&cs48l10_attr_group)) < 0) {
dev_err(cs48l10->dev, "failed to register sysfs\n");
cs48l10_free_gpios(cs48l10);
goto err;
}
ret = cs48l10_spi_speed(cs48l10->spi, CS48L10_SPI_PREBOOT_FREQ);
if (ret < 0) {
dev_err(cs48l10->dev, "failed to set SPI speed\n");
cs48l10_free_gpios(cs48l10);
goto err;
}
dev_info(cs48l10->dev, "%s Register DSP as codec\n",__func__);
ret = snd_soc_register_codec(&spi->dev,
&soc_codec_cs48l10, cs48l10_dai,
ARRAY_SIZE(cs48l10_dai));
if (ret < 0) {
cs48l10_free_gpios(cs48l10);
goto err;
}
dev_info(cs48l10->dev, "Request firmware loading\n");
if ((ret = cs48l10_fw_load_async(cs48l10,
CS48L10_FIRMWARE_NAME)) < 0) {
dev_err(cs48l10->dev, "failed to load FW\n");
cs48l10_free_gpios(cs48l10);
goto err;
}
cs48l10_dsp_if.crus_dsp = cs48l10;
return 0;
err:
clk_disable_unprepare(cs48l10->clk);
kfree(cs48l10);
return ret;
}
static int cs48l10_spi_remove(struct spi_device *spi)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(&spi->dev);
cs48l10_free_gpios(cs48l10);
sysfs_remove_group(&spi->dev.kobj, &cs48l10_attr_group);
if (cs48l10->fw)
kfree(cs48l10->fw);
if (cs48l10->osfw)
release_firmware(cs48l10->osfw);
devm_clk_put(cs48l10->dev, cs48l10->clk);
if (cs48l10)
kfree(cs48l10);
return 0;
}
static const struct spi_device_id cs48l10_spi_id[] = {
{ "cs48l10", 0 },
{ },
};
MODULE_DEVICE_TABLE(spi, cs48l10_spi_id);
static struct spi_driver cs48l10_spi_driver = {
.driver = {
.name = "cs48l10",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
.of_match_table = cs48l10_of_match,
},
.id_table = cs48l10_spi_id,
.probe = cs48l10_spi_probe,
.remove = cs48l10_spi_remove,
};
#if 0
static int cs48l10_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct cs48l10 *cs48l10;
const struct device_node *np = i2c->dev.of_node;
struct cs48l10_platform_data *pdata = dev_get_platdata(&i2c->dev);
int ret = 0;
u32 val = 0;
dev_info(&i2c->dev, "%s %s\n", __func__, dev_name(&i2c->dev));
cs48l10 = kzalloc(sizeof(struct cs48l10), GFP_KERNEL);
if (cs48l10 == NULL)
return -ENOMEM;
cs48l10->i2c = i2c;
i2c_set_clientdata(i2c, cs48l10);
cs48l10->dev = &i2c->dev;
dev_set_drvdata(&i2c->dev, cs48l10);
cs48l10->read_word = cs48l10_read_dsp_word;
cs48l10->write_word = cs48l10_write_dsp_word;
cs48l10->write_word_buf = cs48l10_write_buf;
cs48l10->regmap = regmap_init_i2c(i2c, &cs48l10_regmap_config);
if (IS_ERR(cs48l10->regmap)) {
ret = PTR_ERR(cs48l10->regmap);
dev_err(cs48l10->dev, "Failed to allocate register map: %d\n",
ret);
goto err;
}
mutex_init(&cs48l10->lock);
init_waitqueue_head(&cs48l10->rx_data_wait);
INIT_LIST_HEAD(&cs48l10->unsol_list);
INIT_WORK(&cs48l10->unsol_work, unsol_dsp_mess);
cs48l10->sol_spinlock = __SPIN_LOCK_UNLOCKED(cs48l10->sol_spinlock);
if (pdata) {
cs48l10->pdata.gpio_nreset = pdata->gpio_nreset;
cs48l10->pdata.gpio_int = pdata->gpio_int;
cs48l10->pdata.gpio_busy = pdata->gpio_busy;
} else {
if (of_property_read_u32(np, "nreset-gpios", &val) >= 0)
cs48l10->pdata.gpio_nreset = val;
if (of_property_read_u32(np, "int-gpios", &val) >= 0)
cs48l10->pdata.gpio_int = val;
if (of_property_read_u32(np, "gpio_busy", &val) >= 0)
cs48l10->pdata.gpio_busy = val;
}
/* setup platform RESET/INT/BUSY functions */
if ((ret = cs48l10_setup_gpios(cs48l10)) < 0) {
dev_err(cs48l10->dev, "Error: Can't set gpios. ret = %d\n", ret);
goto err;
}
if ((ret = sysfs_create_group(&i2c->dev.kobj,
&cs48l10_attr_group)) < 0) {
dev_err(cs48l10->dev, "failed to register sysfs\n");
cs48l10_free_gpios(cs48l10);
goto err;
}
dev_info(cs48l10->dev, "%s Register DSP as codec",__func__);
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_cs48l10, cs48l10_dai,
ARRAY_SIZE(cs48l10_dai));
if (ret < 0) {
cs48l10_free_gpios(cs48l10);
goto err;
}
dev_info(cs48l10->dev, "Request firmware loading\n");
if ((ret = cs48l10_fw_load_async(cs48l10,
CS48L10_FIRMWARE_NAME)) < 0) {
dev_err(cs48l10->dev, "failed to load FW\n");
cs48l10_free_gpios(cs48l10);
goto err;
}
cs48l10_dsp_if.crus_dsp = cs48l10;
return 0;
err:
kfree(cs48l10);
return ret;
}
static int cs48l10_i2c_remove(struct i2c_client *i2c)
{
struct cs48l10 *cs48l10 = dev_get_drvdata(&i2c->dev);
cs48l10_free_gpios(cs48l10);
sysfs_remove_group(&i2c->dev.kobj, &cs48l10_attr_group);
if (cs48l10->fw)
kfree(cs48l10->fw);
if (cs48l10->osfw)
release_firmware(cs48l10->osfw);
if (cs48l10)
kfree(cs48l10);
return 0;
}
static const struct i2c_device_id cs48l10_id_table[] = {
{ "cs48l10", 0 },
{}
};
MODULE_DEVICE_TABLE(i2c, pm860x_id_table);
static struct i2c_driver cs48l10_i2c_driver = {
.driver = {
.name = "cs48l10",
.owner = THIS_MODULE,
.of_match_table = cs48l10_of_match,
},
.probe = cs48l10_i2c_probe,
.remove = cs48l10_i2c_remove,
.id_table = cs48l10_id_table,
};
#endif
static int __init cs48l10_init(void)
{
/*
* int ret;
* ret = i2c_add_driver(&cs48l10_i2c_driver);
* if (ret != 0) {
* pr_err("Failed to register CS48L10 I2C driver: %d\n", ret);
* return ret;
* }
*/
return spi_register_driver(&cs48l10_spi_driver);
}
static void __exit cs48l10_exit(void)
{
/*i2c_del_driver(&cs48l10_i2c_driver);*/
spi_unregister_driver(&cs48l10_spi_driver);
}
module_init(cs48l10_init);
module_exit(cs48l10_exit);
MODULE_AUTHOR("Georgi Vlaev, Nucleus Systems, Ltd, <joe@nucleusys.com>");
MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc., <phandrigan@cirrus.com>");
MODULE_DESCRIPTION("CS48L10 Audio DSP Driver");
MODULE_LICENSE("GPL");