/* * drivers/spi/spi-im501.c * (C) Copyright 2014-2018 * Fortemedia, Inc. * * Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. * * Author: HenryZhang ; * LiFu * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "im501.h" #include "spi-im501.h" #ifdef CUBIETRUCK #include #else #include #include #include #endif //#define SHOW_DL_TIME #ifdef SHOW_DL_TIME struct timex txc; struct timex txc2; struct timeval tv; struct rtc_time rt; #endif //Fuli 20161215 define spi speed as constant #define SPI_LOW_SPEED 1000000 #ifdef CUBIETRUCK #define SPI_HIGH_SPEED 6000000 #else #define SPI_HIGH_SPEED 20000000 #endif // #define FW_RD_CHECK #define FW_BURST_RD_CHECK //#define FW_MEM_RD_CHECK //#define ENABLE_KERNEL_DUMP #define ENABLE_KFIFO #define MAX_KFIFO_BUFFER_SIZE (131072*2) /* >4 seconds */ #define CODEC2IM501_PDM_CLKI //fuli 20170629 for new protocol of oneshot #define SM501 // void enable_pdm_clock(void); void disable_pdm_clock(void); static atomic_t dmic_clk_cnt = ATOMIC_INIT(0); int im501_dsp_mode_old = -1; int im501_vbuf_trans_status; int ap_sleep_flag; static struct spi_device *im501_spi; int current_firmware_type = WAKEUP_FIRMWARE; u8 firmware_type = WAKEUP_FIRMWARE; //0: wakeup firmware; 1: ultrasound firmware //#define VERIFY_PCM_KFIFO #ifdef VERIFY_PCM_KFIFO #define PCM_FILE "im501_oneshot_rec.pcm" u8 *g_pcm_buf; u32 g_pcm_size; u32 g_offset; #endif //VERIFY_PCM_KFIFO #define IM501_CDEV_NAME "im501_pcm" #define REC_FILE_PATH "/data/im501_oneshot_kernel.pcm" #define SPI_REC_FILE_PATH "/data/im501_spi_rec_kernel.pcm" struct im501_data_t { struct mutex lock; struct mutex spi_op_lock; dev_t record_chrdev; struct cdev record_cdev; struct device *record_dev; struct kfifo *pcm_kfifo; spinlock_t pcm_fifo_lock; struct class *cdev_class; atomic_t audio_owner; int irq_gpio; int reset_gpio; }; struct im501_data_t *im501_data; int im501_irq; static struct work_struct im501_irq_work; static struct work_struct im501_fw_load_work; static struct workqueue_struct *im501_irq_wq; static int im501_host_irqstatus; // To notify the HAL about the incoming irq. //Fuli 20161216 resolve the issue about can only invoke interrupt once static void im501_irq_handling_work(struct work_struct *work); // //Fuli 20170922 to support SPI recording static char im501_spi_record_started; struct file *fpdata = (struct file *)-1; mm_segment_t oldfs; static loff_t offset; unsigned int output_counter; int check_dsp_status(void); int im501_spi_read_reg(u8 reg, u8 *val) { struct spi_message message; struct spi_transfer x[2]; int status; u8 write_buf[2]; u8 read_buf[1]; write_buf[0] = IM501_SPI_CMD_REG_RD; write_buf[1] = reg & 0xff; spi_message_init(&message); memset(x, 0, sizeof(x)); x[0].len = 2; x[0].tx_buf = write_buf; spi_message_add_tail(&x[0], &message); x[1].len = 1; x[1].rx_buf = read_buf; spi_message_add_tail(&x[1], &message); status = spi_sync(im501_spi, &message); *val = read_buf[0]; return status; } EXPORT_SYMBOL_GPL(im501_spi_read_reg); int im501_spi_write_reg(u8 reg, u8 val) { int status; u8 write_buf[3]; write_buf[0] = IM501_SPI_CMD_REG_WR; write_buf[1] = reg; write_buf[2] = val; status = spi_write(im501_spi, write_buf, sizeof(write_buf)); if (status) dev_err(&im501_spi->dev, "%s error %d\n", __func__, status); return status; } EXPORT_SYMBOL_GPL(im501_spi_write_reg); int im501_spi_read_dram(u32 addr, u8 *pdata) { struct spi_message message; struct spi_transfer x[2]; int status, i; u8 write_buf[6]; u8 read_buf[4]; u8 spi_cmd = IM501_SPI_CMD_DM_RD, addr_msb; addr_msb = (addr >> 24) & 0xff; if (addr_msb == 0x10) { spi_cmd = IM501_SPI_CMD_IM_RD; } else if (addr_msb == 0xF) { spi_cmd = IM501_SPI_CMD_DM_RD; } write_buf[0] = spi_cmd; write_buf[1] = addr & 0xFF; write_buf[2] = (addr >> 8) & 0xFF; write_buf[3] = (addr >> 16) & 0xFF; write_buf[4] = 2; write_buf[5] = 0; spi_message_init(&message); memset(x, 0, sizeof(x)); x[0].len = 6; x[0].tx_buf = write_buf; spi_message_add_tail(&x[0], &message); x[1].len = 4; x[1].rx_buf = read_buf; spi_message_add_tail(&x[1], &message); status = spi_sync(im501_spi, &message); for (i = 0; i < 4; i++) { *(pdata + i) = *(read_buf + i); } return status; } EXPORT_SYMBOL_GPL(im501_spi_read_dram); int im501_spi_write_dram(u32 addr, u8 *pdata) { int status; u8 buf[10]; buf[0] = IM501_SPI_CMD_DM_WR; buf[1] = addr & 0xFF; buf[2] = (addr >> 8) & 0xFF; buf[3] = (addr >> 16) & 0xFF; buf[4] = 2; //2 words buf[5] = 0; buf[6] = *pdata; buf[7] = *(pdata + 1); buf[8] = *(pdata + 2); buf[9] = *(pdata + 3); status = spi_write(im501_spi, buf, sizeof(buf)); return status; } EXPORT_SYMBOL_GPL(im501_spi_write_dram); /** * im501_spi_burst_read_dram - Read data from SPI by im501 dsp memory address. * @addr: Start address. * @rxbuf: Data Buffer for reading. * @len: Data length, it must be a multiple of 8. * * Returns true for success. */ int im501_spi_burst_read_dram(u32 addr, u8 *rxbuf, size_t len) { int status; u8 spi_cmd = IM501_SPI_CMD_DM_RD, addr_msb; u8 write_buf[6]; unsigned int end, offset = 0; struct spi_message message; struct spi_transfer x[2]; addr_msb = (addr >> 24) & 0xff; if (addr_msb == 0x10) { spi_cmd = IM501_SPI_CMD_IM_RD; } else if (addr_msb == 0xF) { spi_cmd = IM501_SPI_CMD_DM_RD; } while (offset < len) { if (offset + IM501_SPI_BUF_LEN <= len) end = IM501_SPI_BUF_LEN; else end = len % IM501_SPI_BUF_LEN; write_buf[0] = spi_cmd; write_buf[1] = ((addr + offset) & 0x000000ff) >> 0; write_buf[2] = ((addr + offset) & 0x0000ff00) >> 8; write_buf[3] = ((addr + offset) & 0x00ff0000) >> 16; write_buf[4] = (end >> 1) & 0xff; write_buf[5] = (end >> (1 + 8)) & 0xff; spi_message_init(&message); memset(x, 0, sizeof(x)); x[0].len = 6; x[0].tx_buf = write_buf; spi_message_add_tail(&x[0], &message); x[1].len = end; x[1].rx_buf = rxbuf + offset; spi_message_add_tail(&x[1], &message); status = spi_sync(im501_spi, &message); if (status) { dev_err(&im501_spi->dev, "%s: error in spi_sync\n", __func__); return false; } offset += IM501_SPI_BUF_LEN; } return 0; } EXPORT_SYMBOL_GPL(im501_spi_burst_read_dram); //Fuli 20161215 extract a invert copy function for burst write int im501_8byte_invert_copy(u8 *source, u8 *target) { int i = 0; if (source == NULL || target == NULL) return -1; for (i = 0; i < 8; i++) { target[7 - i] = source[i]; } return 0; } // //Fuli 20161215 extract a copy function for burst write int im501_8byte_copy(u8 *source, u8 *target) { int i = 0; if (source == NULL || target == NULL) return -1; for (i = 0; i < 8; i++) { target[i] = source[i]; } return 0; } // /** * im501_spi_burst_write - Write data to SPI by im501 dsp memory address. * @addr: Start address. * @txbuf: Data Buffer for writng. * @len: Data length, it must be a multiple of 8. * @type: Firmware type, MSB and LSB, the iM501 firmware is in MSB, but the EFT firmware is in LSB. * * Returns true for success. */ int im501_spi_burst_write(u32 addr, const u8 *txbuf, size_t len, int fw_type) { u8 spi_cmd, *write_buf, *local_buf; u32 offset = 0; int i, local_len, end, status; spi_cmd = (addr == 0x10000000) ? IM501_SPI_CMD_IM_WR : IM501_SPI_CMD_DM_WR; local_len = (len % 8) ? (((len / 8) + 1) * 8) : len; local_buf = kzalloc(local_len, GFP_KERNEL); if (local_buf == NULL) return -ENOMEM; memset(local_buf, 0, local_len); memcpy(local_buf, txbuf, len); write_buf = kzalloc(IM501_SPI_BUF_LEN + 6, GFP_KERNEL); if (write_buf == NULL) { kfree(local_buf); return -ENOMEM; } write_buf[0] = spi_cmd; //Assign the command byte first, since it is fixed. while (offset < local_len) { if (offset + IM501_SPI_BUF_LEN <= local_len) { end = IM501_SPI_BUF_LEN; } else { end = local_len % IM501_SPI_BUF_LEN; } write_buf[1] = ((addr + offset) & 0x000000ff) >> 0; //The memory address write_buf[2] = ((addr + offset) & 0x0000ff00) >> 8; write_buf[3] = ((addr + offset) & 0x00ff0000) >> 16; write_buf[4] = ((end / 2) & 0x00ff) >> 0; //The word counter low byte. write_buf[5] = ((end / 2) & 0xff00) >> 8; //Assign the high order word counter, since it is fixed. if (fw_type == IM501_DSP_FW) { for (i = 0; i < end; i += 8) { im501_8byte_invert_copy(local_buf + offset + i, write_buf + i + 6); } } else if (fw_type == IM501_EFT_FW) { for (i = 0; i < end; i += 8) { im501_8byte_copy(local_buf + offset + i, write_buf + i + 6); } } status = spi_write(im501_spi, write_buf, end + 6); if (status) { dev_err(&im501_spi->dev, "%s error %d\n", __func__, status); kfree(write_buf); kfree(local_buf); return status; } offset += IM501_SPI_BUF_LEN; } kfree(write_buf); kfree(local_buf); return 0; } EXPORT_SYMBOL_GPL(im501_spi_burst_write); //read frame counter to check DSP is working or not int check_dsp_status(void) { u32 framecount1, framecount2; int err = ESUCCESS; err = im501_spi_read_dram(TO_DSP_FRAMECOUNTER_ADDR, (u8 *) &framecount1); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: call im501_spi_read_dram() return error(%d)\n", __func__, err); return err; } msleep(50); err = im501_spi_read_dram(TO_DSP_FRAMECOUNTER_ADDR, (u8 *) &framecount2); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: call im501_spi_read_dram() return error(%d)\n", __func__, err); return err; } framecount1 = framecount1 >> 16; framecount2 = framecount2 >> 16; dev_dbg(&im501_spi->dev, "%s: framecount1=%x, framecount2=%x\n", __func__, framecount1, framecount2); if (framecount1 != framecount2) { dev_info(&im501_spi->dev, "%s: im501 is working normally.\n", __func__); } else { dev_err(&im501_spi->dev, "%s: im501 is NOT working.\n", __func__); return EDSPNOTWORK; } return ESUCCESS; } /** * This function should be implemented by customers as a callback function. * The following is just a sample to control the PDM_CLKI on/off through UIF. * The parameter @pdm_clki_rate is reserved for future use. */ int codec2im501_pdm_clki_set(bool set_flag, u32 rate) { if (set_flag == NORMAL_MODE) { dev_dbg(&im501_spi->dev, "%s: enable pdm_clki rate %d\n", __func__, rate); enable_pdm_clock(); msleep(20); } else { dev_dbg(&im501_spi->dev, "%s: disable pdm_clki rate %d\n", __func__, rate); disable_pdm_clock(); msleep(20); } return 0; } int im501_reset(void) { if (im501_data->reset_gpio == -1) return 0; dev_info(&im501_spi->dev, "%s: reset im501 with gpio_number = %d\n", __func__, im501_data->reset_gpio); dev_dbg(&im501_spi->dev, "%s: set reset pin to low.\n", __func__); gpio_set_value(im501_data->reset_gpio, 0); msleep(20); dev_dbg(&im501_spi->dev, "%s: set reset pin to high.\n", __func__); gpio_set_value(im501_data->reset_gpio, 1); msleep(20); return 0; } void writeDataFile(char *buf, int count) { loff_t ret; if (!IS_ERR(fpdata)) { oldfs = get_fs(); set_fs(get_ds()); dev_dbg(&im501_spi->dev, "writing %d\n", (int)count); ret = vfs_write(fpdata, buf, count, &offset); set_fs(oldfs); } } void openFileForWrite(unsigned char *filePath) { #ifdef ENABLE_KERNEL_DUMP offset = 0; oldfs = get_fs(); set_fs(get_ds()); if (!IS_ERR(fpdata)) { filp_close(fpdata, NULL); fpdata = NULL; } fpdata = filp_open(filePath, O_CREAT | O_RDWR, 0666); if (IS_ERR(fpdata)) dev_err(&im501_spi->dev, "file open failed %s\n", filePath); else dev_info(&im501_spi->dev, "file open success %s\n", filePath); set_fs(oldfs); #endif } void closeDataFile(void) { if (!IS_ERR(fpdata)) { oldfs = get_fs(); set_fs(get_ds()); filp_close(fpdata, NULL); fpdata = (struct file *)-1; set_fs(oldfs); } } unsigned char fetch_voice_data(unsigned int start_addr, unsigned int data_length) { unsigned char err = ESUCCESS; unsigned char *pbuf; unsigned int i, a, b; unsigned int read_size = 2048; #ifdef SHOW_DL_TIME do_gettimeofday(&(txc.time)); #endif a = data_length / read_size; b = data_length % read_size; //dev_err(&im501_spi->dev, "%s: data_length=%d, a=%d, b=%d\n", __func__, data_length, a, b); pbuf = (unsigned char *)kzalloc(read_size, GFP_KERNEL); if (!pbuf) { dev_err(&im501_spi->dev, "%s: pbuf allocation failure.\n", __func__); return -1; } for (i = 0; i < a; i++) { err = im501_spi_burst_read_dram(start_addr, pbuf, read_size); if (err != NO_ERR) { if (pbuf) kfree(pbuf); dev_err(&im501_spi->dev, "%s: im501_spi_burst_read_dram() failure.\n", __func__); return err; } start_addr += read_size; #ifdef ENABLE_KERNEL_DUMP if (!IS_ERR(fpdata)) writeDataFile(pbuf, read_size); #endif //fuli 20170515 to reduce output dev_err(&im501_spi->dev, "%s: put the voice data to pcm FIFO.\n", __func__); //if(i % 10 == 0) dev_err(&im501_spi->dev, "%s: put the voice data to pcm FIFO.\n", __func__); // #ifdef ENABLE_KFIFO if (!kfifo_is_full(im501_data->pcm_kfifo)) { spin_lock(&im501_data->pcm_fifo_lock); kfifo_in(im501_data->pcm_kfifo, pbuf, read_size); spin_unlock(&im501_data->pcm_fifo_lock); } else { dev_err(&im501_spi->dev, "%s: PCM FIFO is full.\n", __func__); } #endif } if (b > 0) { err = im501_spi_burst_read_dram(start_addr, pbuf, b); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: im501_spi_burst_read_dram failure with %d.\n", __func__, err); if (pbuf) kfree(pbuf); return err; } #ifdef ENABLE_KERNEL_DUMP if (!IS_ERR(fpdata)) writeDataFile(pbuf, b); #endif #ifdef ENABLE_KFIFO if (!kfifo_is_full(im501_data->pcm_kfifo)) { spin_lock(&im501_data->pcm_fifo_lock); kfifo_in(im501_data->pcm_kfifo, pbuf, b); spin_unlock(&im501_data->pcm_fifo_lock); } else { dev_err(&im501_spi->dev, "%s: PCM FIFO is full.\n", __func__); } #endif } if (pbuf) kfree(pbuf); #ifdef SHOW_DL_TIME do_gettimeofday(&(txc2.time)); dev_err(&im501_spi->dev, "%s: get one bank used %ld us\n", __func__, (unsigned long)((unsigned long)(txc2.time.tv_sec - txc.time.tv_sec) * 1000000L + (unsigned long)(txc2.time.tv_usec - txc.time.tv_usec))); #endif return err; } EXPORT_SYMBOL_GPL(fetch_voice_data); unsigned char parse_to_host_command(to_host_cmd cmd) { unsigned char err = ESUCCESS; unsigned int address = HW_VOICE_BUF_START; u8 *pdata; u32 spi_speed = SPI_HIGH_SPEED; int ret; char *hotword_str[2] = { "IM501_HOTWORD", NULL }; //fuli 20170629 for new protocol int bank, sync_word_pos, data_size = HW_VOICE_BUF_BANK_SIZE; // //dev_err(&im501_spi->dev, "%s: cmd_byte = %#x\n", __func__, cmd.cmd_byte); if ((im501_vbuf_trans_status == 1) && (cmd.status == 1) && (cmd.cmd_byte == TO_HOST_CMD_DATA_BUF_RDY)) { //Reuest host to read To-Host Buffer-Fast //dev_err(&im501_spi->dev, "%s: data ready\n", __func__); #ifdef SM501 //fuli 20170629 the protocol is different from old version data_size = ((cmd.attri >> 13) & 0x7FF) << 1; //16 bits sample, one sample occupies 2 bytes. bank = (cmd.attri >> 11) & 0x03; sync_word_pos = cmd.attri & 0x7FF; if ((bank == BANK0) || (bank == BANK0_SYNC)) { address = HW_VOICE_BUF_BANK0; } else if ((bank == BANK1) || (bank == BANK1_SYNC)) { address = HW_VOICE_BUF_BANK1; } #else //im502BE //fuli 20170629 no use voice_buf_data.index = (cmd.attri >>8) & 0xFFFF; //package index if ((cmd.attri & 0xFF) == 0xF0) { address = HW_VOICE_BUF_START; //BANK0 address } else if ((cmd.attri & 0xFF) == 0xF9) { address = HW_VOICE_BUF_START + HW_VOICE_BUF_BANK_SIZE; //BANK1 address } #endif if (im501_host_irqstatus == 1) dev_dbg(&im501_spi->dev, "%s: data ready at address=%x, data_size=%x\n", __func__, address, data_size); err = fetch_voice_data(address, data_size); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: fetch_voice_data() return error.\n", __func__); return err; } //cmd.status = 0; //Should be modified in accordance with customers scenario definitions. pdata = (u8 *) &cmd; pdata[3] &= 0x7F; //changes the bit-31 to 0 to indicate that the hot has finished with the task. //dev_err(&im501_spi->dev, "%s: pdata[%#x, %#x, %#x, %#x]\n", __func__, pdata[0], pdata[1], pdata[2], pdata[3]); err = im501_spi_write_dram(TO_HOST_CMD_ADDR, pdata); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: im501_spi_write_dram() return error.\n", __func__); return err; } } else { //treat all other interrupt as keyword detect //if(cmd.cmd_byte == TO_HOST_CMD_KEYWORD_DET) {//Info host Keywords detected //dev_err(&im501_spi->dev, "%s: im501_vbuf_trans_status = %d\n", __func__, im501_vbuf_trans_status); if ((im501_vbuf_trans_status == 0) && (im501_dsp_mode_old == POWER_SAVING_MODE)) { //only available when not transfer voice and in PSM im501_vbuf_trans_status = 1; im501_host_irqstatus = 1; #ifdef CODEC2IM501_PDM_CLKI //to change the iM501 from PSM to Normal mode, and to keep the same setting //as the mode before changing to PSM, the Host just needs to turn on the PDMCLKI, //and no additional command is needed. codec2im501_pdm_clki_set(NORMAL_MODE, 0); im501_dsp_mode_old = NORMAL_MODE; mdelay(5); #endif dev_info(&im501_spi->dev, "%s: keyword detected.\n", __func__); //dev_err(&im501_spi->dev, "%s: ap_sleep_flag = %d, im501_dsp_mode_old = %d\n", __func__, ap_sleep_flag, im501_dsp_mode_old); if (ap_sleep_flag) { im501_spi->mode = SPI_MODE_0; /* clk active low */ im501_spi->bits_per_word = 8; im501_spi->max_speed_hz = spi_speed; //SPI_HIGH_SPEED ret = spi_setup(im501_spi); if (ret < 0) { dev_err(&im501_spi->dev, "spi_setup() failed\n"); return -EIO; } msleep(20); ap_sleep_flag = 0; } //cmd.status = 0; //Should be modified in accordance with customers scenario definitions. pdata = (u8 *) &cmd; pdata[3] &= 0x7F; //changes the bit-31 to 0 to indicate that the hot has finished with the task. //dev_err(&im501_spi->dev, "%s: pdata[%#x, %#x, %#x, %#x]\n", __func__, pdata[0], pdata[1], pdata[2], pdata[3]); err = im501_spi_write_dram(TO_HOST_CMD_ADDR, pdata); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: 1111 im501_spi_write_dram() return error.\n", __func__); return err; } //dev_err(&im501_spi->dev, "%s: start to get oneshot and save to file!!!!!!!!!!!!!!!!!!!!!!\n", __func__); openFileForWrite(REC_FILE_PATH); request_start_voice_buf_trans(); kobject_uevent_env(&im501_spi->dev.kobj, KOBJ_CHANGE, hotword_str); } else { dev_info(&im501_spi->dev, "%s: Do not process other interrupt during process " "keyword detection and oneshot, or DSP is in normal mode.\n", __func__); } } return err; } unsigned char im501_send_to_dsp_command(to_501_cmd cmd) { unsigned char err; unsigned int i; u8 pTempData[8]; u8 *pdata; pdata = (u8 *) &cmd; //dev_err(&im501_spi->dev, "%s: attr=%#x, ext=%#x, status=%#x, cmd=%#x\n", __func__, cmd.attri, cmd.cmd_byte_ext, cmd.status, cmd.cmd_byte); err = im501_spi_write_dram(TO_DSP_CMD_ADDR, pdata); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: call im501_spi_write_dram() return error(%d)\n", __func__, err); return err; } err = im501_spi_write_reg(0x01, cmd.cmd_byte); //generate interrupt to DSP if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: call im501_spi_write_reg() return error(%d)\n", __func__, err); return err; } memset(pTempData, 0, 8); for (i = 0; i < 200; i++) { //wait for (50*100us = 5ms) to check if DSP finished err = im501_spi_read_dram(TO_DSP_CMD_ADDR, pTempData); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: call im501_spi_read_dram() return error(%d)\n", __func__, err); return err; } //dev_err(&im501_spi->dev, "%s: pTempData[%#x, %#x, %#x, %#x]\n", __func__, pTempData[0], pTempData[1], pTempData[2], pTempData[3]); cmd.status = pTempData[3] >> 7; cmd.cmd_byte_ext = pTempData[3] & 0x7F; cmd.attri = pTempData[0] | (pTempData[1] * 256) | (pTempData[2] * 256 * 256); dev_info(&im501_spi->dev, "%s: cmd[%#x, %#x, %#x]\n", __func__, cmd.status, cmd.cmd_byte_ext, cmd.attri); if (cmd.status != 0) { err = TO_501_CMD_ERR; } else { err = ESUCCESS; break; } msleep(20); } return err; } unsigned char im501_send_message_to_dsp(unsigned char cmd_index, unsigned int para) { unsigned char err = ESUCCESS; to_501_cmd cmd; cmd.attri = para & 0xFFFFFF; cmd.cmd_byte_ext = (para >> 24) & 0x7F; cmd.status = 1; cmd.cmd_byte = cmd_index; //((cmd_index & 0x3F) << 2) | 0x01; //D[1] : "1", interrupt DSP. This bit generates NMI (non-mask-able interrupt), D[0]: "1" generate mask-able interrupt dev_dbg(&im501_spi->dev, "%s: attri=%#x, cmd_byte_ext=%#x, status=%#x, cmd_byte=%#x\n", __func__, cmd.attri, cmd.cmd_byte_ext, cmd.status, cmd.cmd_byte); err = im501_send_to_dsp_command(cmd); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: fail to send message(%02x) to dsp. err=%d\n", __func__, cmd_index >> 2, err); } return err; } unsigned char request_start_voice_buf_trans(void) { unsigned char err = ESUCCESS; err = im501_send_message_to_dsp(TO_DSP_CMD_REQ_START_BUF_TRANS, 0); //Fuli 20161216 clear fifo when start to detect ketwords. dev_info(&im501_spi->dev, "%s: clear pcm fifo", __func__); spin_lock(&im501_data->pcm_fifo_lock); kfifo_reset(im501_data->pcm_kfifo); spin_unlock(&im501_data->pcm_fifo_lock); return err; } unsigned char request_stop_voice_buf_trans(void) { unsigned char err = ESUCCESS; dev_info(&im501_spi->dev, "%s: stop voice transfer\n", __func__); err = im501_send_message_to_dsp(TO_DSP_CMD_REQ_STOP_BUF_TRANS, 0); im501_vbuf_trans_status = 0; return err; } unsigned char request_enter_psm(void) { unsigned char err = ESUCCESS; if (im501_vbuf_trans_status == 1) { // stop voice transfer. request_stop_voice_buf_trans(); } im501_vbuf_trans_status = 0; if (im501_dsp_mode_old != POWER_SAVING_MODE) { //The current dsp mode is not PSM. dev_info(&im501_spi->dev, "ap_sleep_flag = %d, im501_dsp_mode_old = %d. iM501 is going to sleep.\n", ap_sleep_flag, im501_dsp_mode_old); dev_dbg(&im501_spi->dev, "%s: the command is %#x\n", __func__, TO_DSP_CMD_REQ_ENTER_PSM); err = im501_send_message_to_dsp(TO_DSP_CMD_REQ_ENTER_PSM, 0); if (ESUCCESS != err) { dev_err(&im501_spi->dev, "%s: error occurs when switch to power saving mode, error = %d\n", __func__, err); } #ifdef CODEC2IM501_PDM_CLKI mdelay(100); codec2im501_pdm_clki_set(POWER_SAVING_MODE, 0); mdelay(5); #endif ap_sleep_flag = 1; im501_dsp_mode_old = POWER_SAVING_MODE; dev_dbg(&im501_spi->dev, "%s: ap_sleep_flag = %d, im501_dsp_mode_old = %d\n", __func__, ap_sleep_flag, im501_dsp_mode_old); } return err; } unsigned char request_enter_normal(void) { unsigned char err = ESUCCESS; dev_info(&im501_spi->dev, "%s: entering...\n", __func__); if (im501_vbuf_trans_status == 1) { // stop voice transfer. request_stop_voice_buf_trans(); } im501_vbuf_trans_status = 0; if (im501_dsp_mode_old != NORMAL_MODE) { //The current dsp mode is not NORMAL #ifdef CODEC2IM501_PDM_CLKI mdelay(5); codec2im501_pdm_clki_set(NORMAL_MODE, 0); mdelay(100); #endif //fuli 20170629 err = im501_send_message_to_dsp(TO_DSP_CMD_REQ_ENTER_NORMAL, 0); if (ESUCCESS != err) { dev_err(&im501_spi->dev, "%s: error occurs when switch to normal mode, error = %d\n", __func__, err); } ap_sleep_flag = 0; im501_dsp_mode_old = NORMAL_MODE; dev_info(&im501_spi->dev, "%s: ap_sleep_flag = %d, im501_dsp_mode_old = %d\n", __func__, ap_sleep_flag, im501_dsp_mode_old); } return err; } int im501_spi_burst_read_check(u32 start_addr, u8 *buf, u32 data_length) { int err; unsigned char *pbuf; unsigned int i, a, b; dev_info(&im501_spi->dev, "%s: entering...\n", __func__); a = data_length / IM501_SPI_BUF_LEN; b = data_length % IM501_SPI_BUF_LEN; pbuf = (unsigned char *)kzalloc(IM501_SPI_BUF_LEN, GFP_KERNEL); if (!pbuf) { dev_err(&im501_spi->dev, "%s: pbuf allocation failure.\n", __func__); return -1; } for (i = 0; i < a; i++) { dev_dbg(&im501_spi->dev, "%s: start_addr = %#x\n", __func__, start_addr); err = im501_spi_burst_read_dram(start_addr, pbuf, IM501_SPI_BUF_LEN); if (err != ESUCCESS) { return err; } memcpy(buf + i * IM501_SPI_BUF_LEN, pbuf, IM501_SPI_BUF_LEN); start_addr += IM501_SPI_BUF_LEN; } if (b > 0) { err = im501_spi_burst_read_dram(start_addr, pbuf, b); if (err != ESUCCESS) { return err; } memcpy(buf + i * IM501_SPI_BUF_LEN, pbuf, b); } if (pbuf) kfree(pbuf); return err; } EXPORT_SYMBOL_GPL(im501_spi_burst_read_check); #ifdef FW_RD_CHECK static void im501_8byte_swap(u8 *rxbuf, u32 len) { u8 local_buf[8]; int i; for (i = 0; i < len / 8 * 8; i += 8) { local_buf[0] = rxbuf[i + 0]; local_buf[1] = rxbuf[i + 1]; local_buf[2] = rxbuf[i + 2]; local_buf[3] = rxbuf[i + 3]; local_buf[4] = rxbuf[i + 4]; local_buf[5] = rxbuf[i + 5]; local_buf[6] = rxbuf[i + 6]; local_buf[7] = rxbuf[i + 7]; rxbuf[i + 0] = local_buf[7]; rxbuf[i + 1] = local_buf[6]; rxbuf[i + 2] = local_buf[5]; rxbuf[i + 3] = local_buf[4]; rxbuf[i + 4] = local_buf[3]; rxbuf[i + 5] = local_buf[2]; rxbuf[i + 6] = local_buf[1]; rxbuf[i + 7] = local_buf[0]; } } #endif static size_t im501_read_file(char *file_path, const u8 **buf) { loff_t pos = 0; unsigned int file_size = 0; int read_len; struct file *fp; dev_info(&im501_spi->dev, "file_path = %s\n", file_path); fp = filp_open(file_path, O_RDONLY, 0); if (!IS_ERR(fp)) { dev_info(&im501_spi->dev, "The file is present.\n"); file_size = vfs_llseek(fp, pos, SEEK_END); *buf = kzalloc(file_size, GFP_KERNEL); if (*buf == NULL) { filp_close(fp, 0); return 0; } read_len = kernel_read(fp, pos, (char *)*buf, file_size); if (read_len <= 0) { dev_err(&im501_spi->dev, "kernel_read error\n"); read_len = 0; } filp_close(fp, 0); return read_len; } return 0; } static int im501_dsp_load_single_fw_file(u8 *fw_name, u32 addr, int fw_type) { u8 *data; size_t size = 0; #ifdef FW_RD_CHECK u8 *local_buf; int i; #endif #ifdef SHOW_DL_TIME do_gettimeofday(&(txc.time)); rtc_time_to_tm(txc.time.tv_sec, &rt); dev_info(&im501_spi->dev, "%s: start time: %d-%d-%d %d:%d:%d \n", __func__, rt.tm_year + 1900, rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec); #endif size = im501_read_file(fw_name, (const u8 **)&data); if (!size) { dev_err(&im501_spi->dev, "%s: firmware %s open failed.\n", __func__, fw_name); return -1; } if (size) { dev_info(&im501_spi->dev, "%s: firmware %s size = %zu, addr = %#x\n", __func__, fw_name, size, addr); im501_spi_burst_write(addr, data, size, fw_type); #ifdef FW_RD_CHECK local_buf = (u8 *) kzalloc(size, GFP_KERNEL); if (!local_buf) return -ENOMEM; #ifdef FW_BURST_RD_CHECK im501_spi_burst_read_dram(addr, local_buf, size); if (fw_type == IM501_DSP_FW) im501_8byte_swap(local_buf, size); for (i = 0; i < size; i++) { if (local_buf[i] != data[i]) { dev_err(&im501_spi->dev, "%s: fw read %#x vs write %#x @ %#x\n", __func__, local_buf[i], data[i], addr + i); } } #endif kfree(local_buf); #endif kfree(data); } #ifdef SHOW_DL_TIME do_gettimeofday(&(txc.time)); rtc_time_to_tm(txc.time.tv_sec, &rt); dev_info(&im501_spi->dev, "%s: end time: %d-%d-%d %d:%d:%d \n", __func__, rt.tm_year + 1900, rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec); #endif return 0; } EXPORT_SYMBOL_GPL(im501_dsp_load_single_fw_file); static int im501_dsp_load_fw(u8 firmware_type) { //0: wakeup firmware; 1: ultrasound firmware int err = 0; const struct firmware *fw = NULL; int fw_type = IM501_DSP_FW; #ifdef FW_RD_CHECK u8 *local_buf; int i; #endif dev_info(&im501_spi->dev, "%s: firmware_type=%d\n", __func__, firmware_type); #ifdef SHOW_DL_TIME do_gettimeofday(&(txc.time)); rtc_time_to_tm(txc.time.tv_sec, &rt); dev_info(&im501_spi->dev, "%s: start time: %d-%d-%d %d:%d:%d \n", __func__, rt.tm_year + 1900, rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec); #endif if (firmware_type == WAKEUP_FIRMWARE) err = request_firmware(&fw, "im501_iram0.bin", &im501_spi->dev); else err = request_firmware(&fw, "../firmware1/im501_iram0.bin", &im501_spi->dev); if (!fw) { dev_err(&im501_spi->dev, "%s: im501_iram0.bin firmware request failed.\n", __func__); return err; } if (fw) { dev_info(&im501_spi->dev, "%s: firmware im501_iram0.bin size = %zu \n", __func__, fw->size); im501_spi_burst_write(0x10000000, fw->data, fw->size, fw_type); #ifdef FW_RD_CHECK local_buf = (u8 *) kzalloc(fw->size, GFP_KERNEL); if (!local_buf) return -ENOMEM; #ifdef FW_BURST_RD_CHECK im501_spi_burst_read_dram(0x10000000, local_buf, fw->size); if (fw_type == IM501_DSP_FW) im501_8byte_swap(local_buf, fw->size); for (i = 0; i < fw->size; i++) { if (local_buf[i] != fw->data[i]) { dev_err(&im501_spi->dev, "%s: fw read %#x vs write %#x @ %#x\n", __func__, local_buf[i], fw->data[i], 0x10000000 + i); break; } } #endif kfree(local_buf); #endif release_firmware(fw); fw = NULL; } if (firmware_type == WAKEUP_FIRMWARE) err = request_firmware(&fw, "im501_dram0.bin", &im501_spi->dev); else err = request_firmware(&fw, "../firmware1/im501_dram0.bin", &im501_spi->dev); if (!fw) { dev_err(&im501_spi->dev, "%s: im501_dram0.bin firmware request failed.\n", __func__); return err; } if (fw) { dev_info(&im501_spi->dev, "%s: firmware im501_dram0.bin size = %zu\n", __func__, fw->size); im501_spi_burst_write(0x0ffc0000, fw->data, fw->size, fw_type); #ifdef FW_RD_CHECK local_buf = (u8 *) kzalloc(fw->size, GFP_KERNEL); if (!local_buf) return -ENOMEM; #ifdef FW_BURST_RD_CHECK im501_spi_burst_read_dram(0x0ffc0000, local_buf, fw->size); if (fw_type == IM501_DSP_FW) im501_8byte_swap(local_buf, fw->size); for (i = 0; i < fw->size; i++) { if (local_buf[i] != fw->data[i]) { dev_err(&im501_spi->dev, "%s: fw read %#x vs write %#x @ %#x\n", __func__, local_buf[i], fw->data[i], 0x0ffc0000 + i); break; } } #endif kfree(local_buf); #endif release_firmware(fw); fw = NULL; } if (firmware_type == WAKEUP_FIRMWARE) err = request_firmware(&fw, "im501_dram1.bin", &im501_spi->dev); else err = request_firmware(&fw, "../firmware1/im501_dram1.bin", &im501_spi->dev); if (!fw) { dev_err(&im501_spi->dev, "%s: im501_dram1.bin firmware request failed.\n", __func__); return err; } if (fw) { dev_info(&im501_spi->dev, "%s: firmware im501_dram1.bin size = %zu\n", __func__, fw->size); im501_spi_burst_write(0x0ffe0000, fw->data, fw->size, fw_type); #ifdef FW_RD_CHECK local_buf = (u8 *) kzalloc(fw->size, GFP_KERNEL); if (!local_buf) return -ENOMEM; #ifdef FW_BURST_RD_CHECK im501_spi_burst_read_dram(0x0ffe0000, local_buf, fw->size); if (fw_type == IM501_DSP_FW) im501_8byte_swap(local_buf, fw->size); for (i = 0; i < fw->size; i++) { if (local_buf[i] != fw->data[i]) { dev_err(&im501_spi->dev, "%s: fw read %#x vs write %#x @ %#x\n", __func__, local_buf[i], fw->data[i], 0x0ffe0000 + i); break; } } #endif kfree(local_buf); #endif release_firmware(fw); fw = NULL; } #ifdef SHOW_DL_TIME do_gettimeofday(&(txc.time)); rtc_time_to_tm(txc.time.tv_sec, &rt); dev_info(&im501_spi->dev, "%s: end time: %d-%d-%d %d:%d:%d \n", __func__, rt.tm_year + 1900, rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec); #endif dev_info(&im501_spi->dev, "%s: firmware is loaded\n", __func__); return err; } void im501_write_check_spi_reg(u8 reg, u8 val) { u8 ret_val; im501_spi_write_reg(reg, val); im501_spi_read_reg(0x00, (u8 *) &ret_val); if (ret_val != val) dev_err(&im501_spi->dev, "%s: read back value(%x) of reg%02x is different from the written value(%x). \n", __func__, ret_val, reg, val); return; } void enable_pdm_clock(void) { if (atomic_read(&dmic_clk_cnt) > 0) { dev_info(&im501_spi->dev, "PDM clock already enabled\n"); return; } atomic_set(&dmic_clk_cnt, 1); } void disable_pdm_clock(void) { if (atomic_read(&dmic_clk_cnt) > 0) atomic_set(&dmic_clk_cnt, 0); else dev_info(&im501_spi->dev, "PDM clock already disabled\n"); } void im501_start_capture_cb(void) { int err; err = im501_send_message_to_dsp(TO_DSP_CMD_REQ_ENTER_BYPASS, 0x1); if (ESUCCESS != err) { dev_err(&im501_spi->dev, "%s: error when enabling hw bypass mode, error = %d\n", __func__, err); } msleep(20); } static void im501_fw_load(struct work_struct *work) { u32 addr, regval; dev_info(&im501_spi->dev, "%s: entering...\n", __func__); #ifdef CODEC2IM501_PDM_CLKI codec2im501_pdm_clki_set(NORMAL_MODE, 0); mdelay(5); //Apply PDM_CLKI, and then wait for 1024 clock cycles #endif //reset DSP im501_reset(); //Enable DSP Clock, put DSP on hold and remove DSP reset im501_write_check_spi_reg(0x00, 0x07); im501_write_check_spi_reg(0x00, 0x05); mdelay(1); //Enable PDM_DATAO1 addr = 0xFFFFF10; im501_spi_read_dram(addr, (u8 *)®val); regval = regval & 0xFBFFFFFF; im501_spi_write_dram(addr, (u8 *)®val); if (im501_dsp_load_fw(firmware_type) != 0) { dev_err(&im501_spi->dev, "im501 firmware load failed\n"); codec2im501_pdm_clki_set(POWER_SAVING_MODE, 0); return; } mdelay(1); //Remove DSP “on hold” to “run" im501_write_check_spi_reg(0x00, 0x04); //check DSP working or not check_dsp_status(); im501_dsp_mode_old = NORMAL_MODE; //after firmware download, keep DSP in NORMAL mode codec2im501_pdm_clki_set(POWER_SAVING_MODE, 0); return; } static void im501_spi_lock(struct mutex *lock) { mutex_lock(lock); } static void im501_spi_unlock(struct mutex *lock) { mutex_unlock(lock); } //Fuli 20171012 to support SPI recording unsigned char im501_switch_to_spi_recording_mode(void) { return im501_send_message_to_dsp(TO_DSP_CMD_REQ_SWITCH_SPI_REC, 1); } unsigned char im501_switch_to_normal_mode(void) { return im501_send_message_to_dsp(TO_DSP_CMD_REQ_SWITCH_SPI_REC, 0); } static void im501_irq_handling_work(struct work_struct *work) { unsigned char err; to_host_cmd cmd; u8 *pdata; u32 spi_speed = SPI_LOW_SPEED; int ret; //dev_err(&im501_spi->dev, "%s: entering...\n", __func__); if (im501_dsp_mode_old == POWER_SAVING_MODE) { dev_info(&im501_spi->dev, "%s: iM501 is in PSM, set the spi speed to %d\n", __func__, spi_speed); im501_spi->mode = SPI_MODE_0; /* clk active low */ im501_spi->bits_per_word = 8; im501_spi->max_speed_hz = spi_speed; ret = spi_setup(im501_spi); if (ret < 0) { dev_err(&im501_spi->dev, "spi_setup() failed\n"); return; // -EIO; } msleep(20); } pdata = (u8 *) &cmd; err = im501_spi_read_dram(TO_HOST_CMD_ADDR, pdata); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: im501_spi_read_dram error, error=%d\n", __func__, err); return; } if ((im501_host_irqstatus == 1) || ((output_counter % 100) == 0)) { dev_dbg(&im501_spi->dev, "%s: pdata[%#x, %#x, %#x, %#x]\n", __func__, pdata[0], pdata[1], pdata[2], pdata[3]); } output_counter++; cmd.status = pdata[3] >> 7; cmd.cmd_byte = pdata[3] & 0x7F; cmd.attri = pdata[0] | (pdata[1] * 256) | (pdata[2] * 256 * 256); //dev_err(&im501_spi->dev, "%s: cmd[%#x, %#x, %#x]\n", __func__, cmd.status, cmd.cmd_byte, cmd.attri); if (cmd.status != 1) { dev_err(&im501_spi->dev, "%s: got wrong command from DSP.\n", __func__); } im501_spi_lock(&im501_data->spi_op_lock); err = parse_to_host_command(cmd); im501_spi_unlock(&im501_data->spi_op_lock); if (err != ESUCCESS) { dev_err(&im501_spi->dev, "%s: error calling parse_to_host_command(), error=%d\n", __func__, err); } return; } static irqreturn_t im501_irq_handler(int irq, void *para) { bool ret; ret = queue_work(im501_irq_wq, &im501_irq_work); if (!ret) dev_err(&im501_spi->dev, "%s: queue_work failed", __func__); return IRQ_HANDLED; } /* Access to the audio buffer is controlled through "audio_owner". Either the * character device can be opened. */ static int im501_record_open(struct inode *inode, struct file *file) { dev_dbg(&im501_spi->dev, "%s: entering...\n", __func__); if (!atomic_add_unless(&im501_data->audio_owner, 1, 1)) return -EBUSY; dev_dbg(&im501_spi->dev, "%s: im501_data->audio_owner.counter = %d\n", __func__, im501_data->audio_owner.counter); file->private_data = im501_data; return 0; } static int im501_record_release(struct inode *inode, struct file *file) { dev_dbg(&im501_spi->dev, "%s: decrease audio_owner.\n", __func__); atomic_dec(&im501_data->audio_owner); dev_dbg(&im501_spi->dev, "%s: im501_data->audio_owner.counter = %d\n", __func__, im501_data->audio_owner.counter); return 0; } /* The write function is a hack to load the A-model on systems where the * firmware files are not accesible to the user. */ static ssize_t im501_record_write(struct file *file, const char __user *buf, size_t count_want, loff_t *f_pos) { dev_dbg(&im501_spi->dev, "%s: entering...\n", __func__); return count_want; } /* Read out of the kfifo (as long as data is available). */ static ssize_t im501_record_read(struct file *file, char __user *buf, size_t count_want, loff_t *f_pos) { struct im501_data_t *im501_data = (struct im501_data_t *)file->private_data; size_t not_copied; ssize_t to_copy = count_want; int avail; unsigned int copied, total_copied = 0; unsigned long timeout = jiffies + msecs_to_jiffies(500); dev_dbg(&im501_spi->dev, "%s: entering... count_want = %d, *f_pos = %d\n", __func__, (int)count_want, (int)*f_pos); avail = kfifo_len(im501_data->pcm_kfifo); //dev_info(&im501_spi->dev, "%s: avail = %d\n", __func__, avail); while ((total_copied < count_want) && time_before(jiffies, timeout)) { int ret; to_copy = avail; if (count_want - total_copied < avail) to_copy = count_want - total_copied; ret = kfifo_to_user(im501_data->pcm_kfifo, buf + total_copied, to_copy, &copied); if (ret) return -EIO; dev_dbg(&im501_spi->dev, "%s: %d bytes are copied:\n", __func__, copied); total_copied += copied; avail = kfifo_len(im501_data->pcm_kfifo); if ((total_copied < count_want) && !avail) msleep(20); } if (total_copied < count_want) dev_err(&im501_spi->dev, "im501: timeout during reading\n"); not_copied = count_want - total_copied; *f_pos = *f_pos + (count_want - not_copied); return count_want - not_copied; } static const struct file_operations record_fops = { .owner = THIS_MODULE, .open = im501_record_open, .release = im501_record_release, .read = im501_record_read, .write = im501_record_write, }; static int im501_create_cdev(struct spi_device *spi) { int ret = 0, err = -1; struct device *dev = &spi->dev; int cdev_major, dev_no; atomic_set(&im501_data->audio_owner, 0); ret = alloc_chrdev_region(&im501_data->record_chrdev, 0, 1, IM501_CDEV_NAME); if (ret) { dev_err(dev, "%s: failed to allocate character device\n", __func__); return ret; } cdev_major = MAJOR(im501_data->record_chrdev); im501_data->cdev_class = class_create(THIS_MODULE, IM501_CDEV_NAME); if (IS_ERR(im501_data->cdev_class)) { dev_err(dev, "%s: failed to create class\n", __func__); return err; } dev_no = MKDEV(cdev_major, 1); cdev_init(&im501_data->record_cdev, &record_fops); im501_data->record_cdev.owner = THIS_MODULE; ret = cdev_add(&im501_data->record_cdev, dev_no, 1); if (ret) { dev_err(dev, "%s: failed to add character device\n", __func__); return ret; } dev_info(&im501_spi->dev, "%s: cdev_add ok...\n", __func__); im501_data->record_dev = device_create(im501_data->cdev_class, NULL, dev_no, NULL, "%s", IM501_CDEV_NAME); if (IS_ERR(im501_data->record_dev)) { dev_err(&im501_spi->dev, "%s: could not create device\n", __func__); return err; } dev_info(&im501_spi->dev, "%s: device_create ok...\n", __func__); spin_lock_init(&im501_data->pcm_fifo_lock); im501_data->pcm_kfifo = (struct kfifo *)kmalloc(sizeof(struct kfifo), GFP_KERNEL); if (im501_data->pcm_kfifo == NULL) { dev_err(&im501_spi->dev, "no fifo for pcm_kfifo\n"); return -ENOMEM; } ret = kfifo_alloc(im501_data->pcm_kfifo, MAX_KFIFO_BUFFER_SIZE, GFP_KERNEL); if (ret) { dev_err(&im501_spi->dev, "no kfifo memory\n"); return -1; } return ret; } static ssize_t im501_spi_device_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) { char str[5]; size_t ret = 0; char *local_buffer; dev_cmd_mode_gs *get_mode_ret_data; dev_cmd_reg_rw *get_reg_ret_data; dev_cmd_long *get_addr_ret_data; dev_cmd_short *get_irq_status; dev_cmd_fw_type *get_firmware_type_ret_data; int temp = 0; local_buffer = (char *)kzalloc(length * sizeof(char), GFP_KERNEL); if (!local_buffer) { dev_err(&im501_spi->dev, "%s: local_buffer allocation failure.\n", __func__); goto out; } temp = copy_from_user(local_buffer, buffer, length); //dev_err(&im501_spi->dev, "local_buffer = %d:, length = %d\n", local_buffer[0], length); switch (local_buffer[0]) { case FM_SMVD_REG_READ: get_reg_ret_data = (dev_cmd_reg_rw *) local_buffer; im501_spi_read_reg(get_reg_ret_data->reg_addr, (u8 *) &get_reg_ret_data->reg_val); ret = sizeof(dev_cmd_reg_rw); break; case FM_SMVD_DSP_ADDR_READ: get_addr_ret_data = (dev_cmd_long *) local_buffer; im501_spi_read_dram(get_addr_ret_data->addr, (u8 *) &get_addr_ret_data->val); ret = sizeof(dev_cmd_long); break; case FM_SMVD_MODE_GET: get_mode_ret_data = (dev_cmd_mode_gs *) local_buffer; get_mode_ret_data->dsp_mode = (char)im501_dsp_mode_old; ret = sizeof(dev_cmd_mode_gs); break; case FM_SMVD_HOST_IRQQUERY: get_irq_status = (dev_cmd_short *) local_buffer; get_irq_status->val = im501_host_irqstatus; if (im501_host_irqstatus == 1) { im501_host_irqstatus = 0; dev_info(&im501_spi->dev, "%s: iM501 irq is coming..\n", __func__); } ret = sizeof(dev_cmd_short); break; case FM_SMVD_GET_FW_TYPE: get_firmware_type_ret_data = (dev_cmd_fw_type *) local_buffer; get_firmware_type_ret_data->firmware_type = (char)current_firmware_type; ret = sizeof(dev_cmd_fw_type); break; default: ret = sprintf(str, "0"); break; } if (copy_to_user(buffer, local_buffer, ret)) ret = -EFAULT; out: if (local_buffer) kfree(local_buffer); return ret; } static ssize_t im501_spi_device_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) { dev_cmd_long *local_dev_cmd = NULL; dev_cmd_fwdl *local_dev_cmd_fwdl; /*if spi speed is fast enough, we can get total data from SPI together dev_cmd_start_rec *local_rec_cmd; */ dev_cmd_message *local_msg_cmd; unsigned int cmd_name, cmd_addr, cmd_val; int dsp_mode; int new_firmware_type; unsigned char err = ESUCCESS; dev_info(&im501_spi->dev, "%s: entering...\n", __func__); if (im501_dsp_mode_old == -1) { dev_err(&im501_spi->dev, "%s: iM501 firmware not loaded.\n", __func__); err = -EIO; goto out; } local_dev_cmd = (dev_cmd_long *) kzalloc(sizeof(dev_cmd_long), GFP_KERNEL); if (!local_dev_cmd) { dev_err(&im501_spi->dev, "%s: local_dev_cmd allocation failure.\n", __func__); err = -ENOMEM; goto out; } err = copy_from_user(local_dev_cmd, buffer, min(sizeof(dev_cmd_long), length)); if (err) { dev_err(&im501_spi->dev, "%s: copy_from_user error\n", __func__); goto out; } dev_info(&im501_spi->dev, "local_dev_cmd->cmd_name = %d, length = %zu\n", local_dev_cmd->cmd_name, length); cmd_name = local_dev_cmd->cmd_name; switch (cmd_name) { //The short commands case FM_SMVD_REG_WRITE: //Command #1 cmd_addr = local_dev_cmd->addr; cmd_val = local_dev_cmd->val; im501_spi_write_reg(cmd_addr, cmd_val); break; case FM_SMVD_DSP_ADDR_WRITE: //Command #3 cmd_addr = local_dev_cmd->addr; cmd_val = local_dev_cmd->val; im501_spi_write_dram(cmd_addr, (u8 *) &cmd_val); break; case FM_SMVD_MODE_SET: //Command #4 dev_info(&im501_spi->dev, "%s: FM_SMVD_MODE_SET dsp_mode = %d\n", __func__, local_dev_cmd->addr); dsp_mode = local_dev_cmd->addr; // Temporarily set to PSM mode. if (dsp_mode == 0) request_enter_psm(); else if (dsp_mode == 1) request_enter_normal(); else if (dsp_mode == 4) { request_stop_voice_buf_trans(); msleep(2000); closeDataFile(); output_counter = 0; } //im501_dsp_mode_old = dsp_mode; break; case FM_SMVD_DL_EFT_FW: local_dev_cmd_fwdl = (dev_cmd_fwdl *) local_dev_cmd; // Ensure filename is terminated local_dev_cmd_fwdl->buf[sizeof(local_dev_cmd_fwdl->buf)-1] = '\0'; im501_dsp_load_single_fw_file(local_dev_cmd_fwdl->buf, local_dev_cmd_fwdl->dsp_addr, IM501_EFT_FW); break; case FM_SMVD_SWITCH_FW: dev_info(&im501_spi->dev, "%s: FM_SMVD_SWITCH_FW firmware_type = %d\n", __func__, local_dev_cmd->addr); new_firmware_type = local_dev_cmd->addr; if ((new_firmware_type == 0) || (new_firmware_type == 1)) { if (new_firmware_type == current_firmware_type) { dev_err(&im501_spi->dev, "%s: requested firmware type is the same as current firmware type, not need to change it.\n", __func__); } else { firmware_type = new_firmware_type; schedule_work(&im501_fw_load_work); } } else { dev_err(&im501_spi->dev, "%s: requested firmware type is not supported.\n", __func__); } break; //Fuli 20170922 to support SPI recording //do the same as one shot, app and lib will get sample data then generate pcm and wav by channel case FM_SMVD_START_SPI_REC: openFileForWrite(SPI_REC_FILE_PATH); im501_spi_record_started = 1; im501_host_irqstatus = 0; //not wakeup by keywords //it will be set when im501 wakeup from PSM, set it here for SPI recording im501_vbuf_trans_status = 1; request_enter_normal(); im501_switch_to_spi_recording_mode(); request_start_voice_buf_trans(); break; case FM_SMVD_STOP_SPI_REC: request_stop_voice_buf_trans(); im501_spi_record_started = 0; //not sure it is needed or not im501_switch_to_normal_mode(); msleep(1000); request_enter_psm(); closeDataFile(); output_counter = 0; break; //Fuli 20171022 to support general message interface case FM_SMVD_SEND_MESSAGE: local_msg_cmd = (dev_cmd_message *) local_dev_cmd; dev_info(&im501_spi->dev, "%s: will send message 0x%02X to DSP with data 0x%08X.\n", __func__, local_msg_cmd->message_index, local_msg_cmd->message_data); err = im501_send_message_to_dsp( ((local_msg_cmd->message_index & 0x3F) << 2) | 0x01, local_msg_cmd->message_data); break; case FM_SMVD_ENABLE_HOTWORD_DETECT: dev_info(&im501_spi->dev, "%s: enable hotword\n", __func__); codec2im501_pdm_clki_set(NORMAL_MODE, 0); mdelay(100); request_enter_psm(); break; case FM_SMVD_DISABLE_HOTWORD_DETECT: dev_info(&im501_spi->dev, "%s: disable hotword\n", __func__); request_enter_normal(); mdelay(100); codec2im501_pdm_clki_set(POWER_SAVING_MODE, 0); break; default: break; } out: if (local_dev_cmd) kfree(local_dev_cmd); return length; } struct file_operations im501_spi_fops = { .owner = THIS_MODULE, .read = im501_spi_device_read, .write = im501_spi_device_write, }; static struct miscdevice im501_spi_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "im501_spi", .fops = &im501_spi_fops }; //Henry add for try static const struct spi_device_id im501_spi_id[] = { {"im501_spi", 0}, {} }; MODULE_DEVICE_TABLE(spi, im501_spi_id); //End #ifdef CONFIG_OF static struct of_device_id im501_spi_dt_ids[] = { {.compatible = "fortemedia,im501_spi"}, {}, }; MODULE_DEVICE_TABLE(of, im501_spi_dt_ids); #endif static int im501_spi_probe(struct spi_device *spi) { int ret; #ifdef CUBIETRUCK enum gpio_eint_trigtype trigtype; u32 trigenabled = 0; script_item_value_type_e type; script_item_u val; int irq_gpio; /* irq gpio define */ #else struct device *dev = &spi->dev; struct device_node *np = dev->of_node; #endif u32 spi_speed; im501_spi = spi; im501_data = (struct im501_data_t *)kzalloc(sizeof(struct im501_data_t), GFP_KERNEL); if (im501_data == NULL) { dev_err(&im501_spi->dev, "%s: im501_data allocate fail\n", __func__); return -ENOMEM; } mutex_init(&im501_data->spi_op_lock); #ifdef CODEC2IM501_PDM_CLKI codec2im501_pdm_clki_set(POWER_SAVING_MODE, 0); mdelay(5); #endif #ifdef CUBIETRUCK type = script_get_item("spi_board0", "max_speed_hz", &val); if (SCIRPT_ITEM_VALUE_TYPE_INT != type) { dev_err(&im501_spi->dev, "%s: request max_speed_hz error!\n", __func__); spi_speed = SPI_HIGH_SPEED; } else { spi_speed = val.val; } #else ret = of_property_read_u32(np, "spi-max-frequency", &spi_speed); if (ret && ret != -EINVAL) spi_speed = SPI_HIGH_SPEED; #endif spi->mode = SPI_MODE_0; // clk active low spi->bits_per_word = 8; spi->max_speed_hz = spi_speed; ret = spi_setup(spi); if (ret < 0) { dev_err(&spi->dev, "spi_setup() failed\n"); return -EIO; } dev_info(&im501_spi->dev, "%s: setup spi, spi_speed=%d \n", __func__, spi_speed); firmware_type = WAKEUP_FIRMWARE; /*request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, "im501_fw", &spi->dev, GFP_KERNEL, (void *)&firmware_type, im501_fw_loaded); */ im501_create_cdev(spi); ret = misc_register(&im501_spi_dev); if (ret) dev_err(&spi->dev, "Couldn't register control device\n"); im501_vbuf_trans_status = 0; INIT_WORK(&im501_irq_work, im501_irq_handling_work); INIT_WORK(&im501_fw_load_work, im501_fw_load); im501_irq_wq = create_singlethread_workqueue("im501_irq_wq"); #ifdef CUBIETRUCK irq_gpio = GPIOH(16); im501_irq = sw_gpio_irq_request(irq_gpio, TRIG_EDGE_POSITIVE, (peint_handle) im501_irq_handler, NULL); dev_err(&im501_spi->dev, "%s: im501_irq = %d\n", __func__, im501_irq); if (im501_irq == 0) { dev_err(&im501_spi->dev, "%s: IM501 sw_gpio_irq_request failed\n", __func__); return 0; } ret = sw_gpio_eint_get_trigtype(irq_gpio, &trigtype); if (ret != 0) { dev_err(&im501_spi->dev, "%s: sw_gpio_eint_get_trigtype() failed.\n", __func__); } else { if (trigtype == TRIG_EDGE_POSITIVE) { dev_err(&im501_spi->dev, "%s: trigger type is TRIG_EDGE_POSITIVE.\n", __func__); } else { dev_err(&im501_spi->dev, "%s: trigger type is %d\n", __func__, trigtype); } } ret = sw_gpio_eint_get_enable(irq_gpio, &trigenabled); if (ret != 0) { dev_err(&im501_spi->dev, "%s: sw_gpio_eint_get_enable() failed.\n", __func__); } else { dev_err(&im501_spi->dev, "%s: trigger enabled is %d\n", __func__, trigenabled); } #else im501_data->irq_gpio = of_get_gpio(np, 0); if (gpio_request(im501_data->irq_gpio, "im501_irq")) { dev_err(&im501_spi->dev, "irq_gpio %d request failed!\n", im501_data->irq_gpio); return 0; } if (gpio_direction_input(im501_data->irq_gpio)) { dev_err(&im501_spi->dev, "irq_gpio gpio_direction_input failed!\n"); return 0; } im501_irq = gpio_to_irq(im501_data->irq_gpio); irq_set_irq_type(im501_irq, IRQ_TYPE_EDGE_RISING); ret = request_irq(im501_irq, im501_irq_handler, IRQF_TRIGGER_RISING, "im501_spi", NULL); if (ret) { dev_err(&im501_spi->dev, "%s: irq %d request fail!\n", __func__, im501_irq); return ret; } #endif im501_data->reset_gpio = of_get_named_gpio(np, "reset-gpios", 0); if (gpio_request(im501_data->reset_gpio, "im501_reset")) { dev_err(&im501_spi->dev, "reset_gpio %d request failed!\n", im501_data->reset_gpio); return 0; } if (gpio_direction_output(im501_data->reset_gpio, 1)) { dev_err(&im501_spi->dev, "reset_gpio gpio_direction_output failed!\n"); return 0; } schedule_work(&im501_fw_load_work); dev_info(&im501_spi->dev, "%s: success\n", __func__); return 0; } static int im501_spi_remove(struct spi_device *spi) { dev_info(&im501_spi->dev, "%s: entering...\n", __func__); #ifdef CUBIETRUCK sw_gpio_irq_free(im501_irq); #endif flush_workqueue(im501_irq_wq); destroy_workqueue(im501_irq_wq); gpio_free(im501_data->irq_gpio); gpio_free(im501_data->reset_gpio); return 0; } static struct spi_driver im501_spi_driver = { .driver = { .name = "im501_spi", .owner = THIS_MODULE, #ifdef CONFIG_OF .of_match_table = of_match_ptr(im501_spi_dt_ids), #endif }, .probe = im501_spi_probe, .remove = im501_spi_remove, //Henry add for try .id_table = im501_spi_id, //End of try }; //module_spi_driver(im501_spi_driver); static int __init im501_spi_init(void) { int status; status = spi_register_driver(&im501_spi_driver); if (status < 0) { dev_err(&im501_spi->dev, "%s: im501_spi_driver failure. status = %d\n", __func__, status); } dev_info(&im501_spi->dev, "%s: im501_spi_driver success. status = %d\n", __func__, status); return status; } module_init(im501_spi_init); static void __exit im501_spi_exit(void) { spi_unregister_driver(&im501_spi_driver); } module_exit(im501_spi_exit); MODULE_DESCRIPTION("IM501 SPI driver"); MODULE_AUTHOR(", "); MODULE_LICENSE("GPL v2");