| /* |
| * 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"); |