/* * zl380tw.c -- zl380tw ALSA Soc Audio driver * * Copyright 2014 Microsemi Inc. * History: * 2015/02/15 - created driver that combines a CHAR, an ASLSA and a SPI/I2C driver in on single module * 2015/03/24 - Added compatibility to linux kernel 2.6.x to 3.x.x * - Verified with Linux kernels 2.6.38 and 3.10.50 * 2015/04/15 - Added support to multiple zl380tw devices * 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. */ #define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)) #include #include #include #endif #include "zl380tw.h" #ifdef MICROSEMI_HBI_SPI #include #endif #ifdef MICROSEMI_HBI_I2C #include #endif #ifdef ZL380XX_TW_ENABLE_ALSA_CODEC_DRIVER #include #include #include #include #include #include #include #endif #ifdef ZL380XX_TW_ENABLE_CHAR_DEV_DRIVER #include #include #include #endif #include #include #include #include // Required for the wait queues #include #undef ZL38040_SAVE_FWR_TO_FLASH /*undefine if no slave flash is connected to the zl380tw*/ #define MSCCDEBUG #undef MSCCDEBUG2 #ifdef MSCCDEBUG #define TW_DEBUG1(s, args...) \ pr_err("%s %d: "s, __func__, __LINE__, ##args); #else #define TW_DEBUG1(s, args...) #endif #ifdef MSCCDEBUG2 #define TW_DEBUG2(s, args...) \ pr_err("%s %d: "s, __func__, __LINE__, ##args); #else #define TW_DEBUG2(s, args...) #endif struct dtmf_pipe *dtmf_dev;; /*TWOLF HBI ACCESS MACROS-------------------------------*/ #define HBI_PAGED_READ(offset,length) \ ((u16)(((u16)(offset) << 8) | (length))) #define HBI_DIRECT_READ(offset,length) \ ((u16)(0x8000 | ((u16)(offset) << 8) | (length))) #define HBI_PAGED_WRITE(offset,length) \ ((u16)(HBI_PAGED_READ(offset,length) | 0x0080)) #define HBI_DIRECT_WRITE(offset,length) \ ((u16)(HBI_DIRECT_READ(offset,length) | 0x0080)) #define HBI_GLOBAL_DIRECT_WRITE(offset,length) \ ((u16)(0xFC00 | ((offset) << 4) | (length))) #define HBI_CONFIGURE(pinConfig) \ ((u16)(0xFD00 | (pinConfig))) #define HBI_SELECT_PAGE(page) \ ((u16)(0xFE00 | (page))) #define HBI_NO_OP \ ((u16)0xFFFF) /*HBI access type*/ #define TWOLF_HBI_READ 0 #define TWOLF_HBI_WRITE 1 /*HBI address type*/ #define TWOLF_HBI_DIRECT 2 #define TWOLF_HBI_PAGED 3 #define MAX_ALLOWED_BUFFER 50 /* driver private data */ struct zl380tw_priv { #ifdef ZL380XX_TW_ENABLE_CHAR_DEV_DRIVER dev_t t_dev; #endif #ifdef MICROSEMI_HBI_SPI struct spi_device *spi; #endif #ifdef MICROSEMI_HBI_I2C struct i2c_client *i2c; #endif u8 *pData; int sysclk_rate; struct list_head device_entry; struct mutex hbi_lock; }; struct dtmf_pipe { wait_queue_head_t inq, outq; /* read and write queues */ char *buffer, *end; /* begin of buf, end of buf */ int buffersize; /* used in pointer arithmetic */ char *rp, *wp; /* where to read, where to write */ int nreaders, nwriters; /* number of openings for r/w */ struct fasync_struct *async_queue; /* asynchronous readers */ struct semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */ dev_t t_dtmfdev; }; static struct dtmf_pipe *dtmfdatabuffer; #ifdef ZL380XX_TW_ENABLE_CHAR_DEV_DRIVER dev_t t_dev; /*to store the CHAR major number*/ dev_t t_dtmfdev; /*to store the CHAR major number*/ static DECLARE_BITMAP(char_minors, NUMBER_OF_ZL380xx_DEVICES); static DECLARE_BITMAP(char_minors_dtmf, 1); /* For registration of character device */ static struct cdev c_dev; static struct cdev c_dtmfdev; static int module_usage_count; static ioctl_zl380tw zl380tw_ioctl_buf; #endif static unsigned twHBImaxTransferSize =(MAX_TWOLF_ACCESS_SIZE_IN_BYTES); module_param(twHBImaxTransferSize, uint, S_IRUGO); MODULE_PARM_DESC(twHBImaxTransferSize, "total number of data bytes >= 256"); static DECLARE_WAIT_QUEUE_HEAD(wq); static int flag_en = 0; /* if mutual exclusion is required for your particular platform * then add mutex lock/unlock to this driver */ static DEFINE_MUTEX(zl380tw_list_lock); static LIST_HEAD(zl380tw_list); #define DTMF_SUPPORT //sasafkjsahfajskfh #ifdef DTMF_SUPPORT #define MAX_BUF 50 #define DTMF_PIN 167 #define ZL38040_INTR_INDI_REG 0x000 #define ZL38040_INTR_PARA_REG 0x002 #define EVENT_ID_DTMF 0x0005 int dtmf_irq = 0; /******************************************************************************/ int write_dtmf_data_to_buffer(uint8_t dtmfdata); int read_dtmf_buffer(int datalength,char *data); int initialize_dtmf_buffer(void); void show_dtmf_buffer(void); static ssize_t dtmf_write_local(struct dtmf_pipe *dev, const char *buffer, size_t count, loff_t *offset); /* * Global DTMF Driver Buffer * */ unsigned char gDTMFBuffer[MAX_ALLOWED_BUFFER]; struct inode *inodeplocal; /* * DTMF Read Error Database * */ typedef enum { ERROR_NO_DATA_REQUSTED=-1, ERROR_BUFFER_IS_EMPTY=-2, ERROR_BUFFER_IS_FULL=-3, ERROR_READ_INDEX_DATA_MISMATCH=-4, ERROR_READ_REQUESTED_MEMORY_NOT_ACCESSIBLE=-5, ERROR_WRITE_INDEX_DATA_MISMATCH=-6, ERROR_BUFFER_UNKNOWN_ERROR=-30, }ErrorResponses_t; /* * Reset State * */ typedef enum { RESET=0, SET }ResetState_t; /* * Each Location Data State Occupied / Not Occupied * */ typedef enum { /* * Index is Free; Data is Invalid; Dont use if the State Free * */ INDEX_IS_FREE, /* * Data is Occupied; We can read the Data; * Make sure that we are making it as Free after Reading. * */ INDEX_IS_OCCUPIED, }index_data_state_t; /* * Actual DTMF Location * */ typedef struct { /* * Must Check the Data State before reading / Writing * */ index_data_state_t index_data_state; /* * Actual DTMF Data * */ uint8_t data; }DTMFData_t; /* * * */ typedef struct { /* * Write Index; To be updated by the IRQ Function * */ uint8_t write_index; /* * Current Read Index; to be updated by the Read function * */ uint8_t read_index; /* * is the Buffer is Full or Not; One Variable Check * */ uint8_t isbuffer_full; /* * Is the Buffer is empty? one variable Check; * */ uint8_t isbuffer_empty; /* * Total Occupied Data Count * */ uint8_t bufferdatacount; /* * Actual DTMF Data Buffer * */ DTMFData_t dtmfdatabuffer[MAX_ALLOWED_BUFFER]; }DTMF_Database_t; /* * Statically declaring unpacked; We should try using dynamic allocation; * As the DTMF will not be used so much, we can adjust the buffer size * * */ DTMF_Database_t DTMFBufferDatabase; /* * Initialize the DTMF Data Buffer; * Make sure all the indexs are Free to use * */ int initialize_dtmf_buffer(void) { int i=RESET; printk("Resetting the DTMF Buffer\n"); /* * Parent Structure * */ DTMFBufferDatabase.bufferdatacount=RESET; DTMFBufferDatabase.isbuffer_empty=RESET; DTMFBufferDatabase.read_index=RESET; DTMFBufferDatabase.write_index=RESET; /* * Buffer Structure * */ for(i=0;i>>>>>>>index %d,data- %c\n",i,DTMFBufferDatabase.dtmfdatabuffer[i].data); } else { printk(">>>>>>>>Index Data Issue at %d. No dat available\n",i); } } printk("/************************************************************************/\n"); } /* * Write the DTMF Data to the Buffer * */ int write_dtmf_data_to_buffer(uint8_t dtmfdata) { int writedatacount=0; if(DTMFBufferDatabase.isbuffer_full==SET) return ERROR_BUFFER_IS_FULL; if(DTMFBufferDatabase.bufferdatacount>=MAX_ALLOWED_BUFFER) return ERROR_BUFFER_IS_FULL; if(DTMFBufferDatabase.dtmfdatabuffer[DTMFBufferDatabase.write_index].index_data_state==INDEX_IS_OCCUPIED) { /* * No Valid Data Present in the Location; Something went wrong, Notify it back with an Error * */ printk(">>>>>>>>ERROR_WRITE_INDEX_DATA_MISMATCH : Sanju\n"); return ERROR_WRITE_INDEX_DATA_MISMATCH; } /* * Passed the Checks * */ printk("Current Write Index is %d\n",DTMFBufferDatabase.write_index); DTMFBufferDatabase.dtmfdatabuffer[DTMFBufferDatabase.write_index].data=dtmfdata; DTMFBufferDatabase.dtmfdatabuffer[DTMFBufferDatabase.write_index].index_data_state=INDEX_IS_OCCUPIED; DTMFBufferDatabase.write_index++; writedatacount++; if(DTMFBufferDatabase.write_index>=MAX_ALLOWED_BUFFER) { printk("DTMF Driver : Write Index is Resetting\n"); /* * Resetting to the Circular Buffer * */ DTMFBufferDatabase.write_index=RESET; //Resetting to 0 as it is a Circular Buffer } DTMFBufferDatabase.bufferdatacount++; DTMFBufferDatabase.isbuffer_empty=RESET; if(DTMFBufferDatabase.bufferdatacount>=MAX_ALLOWED_BUFFER) { printk("DTMF Driver : Buffer is Full Now\n"); DTMFBufferDatabase.isbuffer_full=SET; } flag_en = 1; wake_up_interruptible(&wq); printk("DTMF Driver : Write to Buffer Success\n"); /* * Return Written Data Count * */ return writedatacount; } /* * read the DTMF Data Buffer * */ int read_dtmf_buffer(int datalength,char *data) { int datacount=0,copieddatacount=RESET; /* * Clearing the requested amount of memory * */ if(data==NULL) { return ERROR_READ_REQUESTED_MEMORY_NOT_ACCESSIBLE; } /* * Resetting the Requested Memory * */ memset(data,RESET,datalength); /* * Add a Memory Lock Here; * Data will clash Otherwise * */ //Add Lock//Make sure that a deadlock will not happen if(datalength==RESET) return ERROR_NO_DATA_REQUSTED; /* * Buffer Count Check * */ printk("######################DTMFBufferDatabase.bufferdatacount = %d\n",DTMFBufferDatabase.bufferdatacount); if(DTMFBufferDatabase.bufferdatacount<=RESET) { printk("######################Inside Else DTMFBufferDatabase.bufferdatacount = %d\n",DTMFBufferDatabase.bufferdatacount); return ERROR_BUFFER_IS_EMPTY; } printk("######################Going to Read 1= %d\n",DTMFBufferDatabase.bufferdatacount); if(DTMFBufferDatabase.isbuffer_empty==SET) return ERROR_BUFFER_IS_EMPTY; printk("######################Going to Read 2 = %d\n",DTMFBufferDatabase.bufferdatacount); //Check the Requested amount of data is avaialble if(datalength<=DTMFBufferDatabase.bufferdatacount) { printk("Requested Data is available\n"); /* * Read Directly the Requested Amount of data and Proceed * */ for(datacount=RESET;datacount>>>>>>>>>Read Index Count %d\n",DTMFBufferDatabase.read_index); DTMFBufferDatabase.read_index++; if(DTMFBufferDatabase.read_index>MAX_ALLOWED_BUFFER) { printk("MAX_READ_ALLOWED Buffer Reached, Resetting\n"); /* *We need to reset the Buffer to 0 * */ DTMFBufferDatabase.read_index=RESET; //Start From the Begining } /* * Decrementing the Buffer Count * */ DTMFBufferDatabase.bufferdatacount--; //One Data We read Already /* * Current Buffer Data Count * */ printk("Current Buffer Count is %d\n",DTMFBufferDatabase.bufferdatacount); if(DTMFBufferDatabase.bufferdatacount<=RESET) { /* * Buffer is Empty; * */ flag_en=0; DTMFBufferDatabase.isbuffer_empty=SET; } } else { return ERROR_READ_INDEX_DATA_MISMATCH; } } return copieddatacount; } else { /* * Requested Amount of Data is not available; But Some data is available We can proceed with that * */ for(datacount=RESET;DTMFBufferDatabase.bufferdatacount>0;datacount++) { /* * Read and Clear the Index * */ if(DTMFBufferDatabase.dtmfdatabuffer[DTMFBufferDatabase.read_index].index_data_state==INDEX_IS_OCCUPIED) { data[copieddatacount++]=DTMFBufferDatabase.dtmfdatabuffer[DTMFBufferDatabase.read_index].data; /* * Index Read; Resettting to Free * */ DTMFBufferDatabase.dtmfdatabuffer[DTMFBufferDatabase.read_index].index_data_state=INDEX_IS_FREE; /* * Incrementing the Read Index * */ DTMFBufferDatabase.read_index++; printk("------------------->>>>>>>>>>Read Index Count %d\n",DTMFBufferDatabase.read_index); if(DTMFBufferDatabase.read_index>MAX_ALLOWED_BUFFER) { /* *We need to reset the Buffer to 0 * */ DTMFBufferDatabase.read_index=RESET; //Start From the Begining } /* * Decrementing the Buffer Count * */ DTMFBufferDatabase.bufferdatacount--; //One Data We read Already if(DTMFBufferDatabase.bufferdatacount<=RESET) { flag_en=0; /* * Buffer is Empty; * */ DTMFBufferDatabase.isbuffer_empty=SET; break; } } else { return ERROR_READ_INDEX_DATA_MISMATCH; } } return copieddatacount; //Returning the Number of Bytes Read } } /******************************************************************************/ struct dtmf_t { uint8_t start_cnt; uint8_t available; char dtmf_buf[50]; }; struct dtmf_t dtmf = {0,0}; int dtmf_cnt = 1; static struct kobject *audioKObj; /** @brief : This function is used to find out the DTMF data ** available or not. ** @retval ssize_t : Return the size of the bytes read. **/ static ssize_t audioDtmfAvaiable( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { printk("audioDtmfAvaiable DTMFBufferDatabase.bufferdatacount = %d\n",DTMFBufferDatabase.bufferdatacount); if(DTMFBufferDatabase.bufferdatacount>0) { return sprintf(buf,"%s","true"); } else { return 0; } #if 0 if(dtmf.available == 1) { return sprintf(buf,"%s","true"); } else { return sprintf(buf,"%s","false"); } #endif } /** ** @brief : This function is used to get DTMF data ** @retval ssize_t : Return the size of the bytes read. **/ static ssize_t dtmfRead( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { char data; printk("dtmfRead DTMFBufferDatabase.bufferdatacount = %d\n",DTMFBufferDatabase.bufferdatacount); wait_event_interruptible(wq, flag_en != 0); flag_en = 0; if(DTMFBufferDatabase.bufferdatacount>0) { //read_dtmf_buffer(1, &data); ssize_t size = sprintf(buf, "%c",data); printk("Data Returning: %s\n",buf); memset(dtmf.dtmf_buf,NULL,50); dtmf.start_cnt = 0; dtmf.available = 0; dtmf_cnt = 0; return 0; } return 0; } static struct kobj_attribute dtmfRead_attribute = __ATTR(dtmfCode, 0660,dtmfRead, NULL); static struct kobj_attribute dtmfavailable_attribute = __ATTR(dtmfAvaiable, 0660, audioDtmfAvaiable, NULL); static struct attribute *attrs[] = { &dtmfRead_attribute.attr, &dtmfavailable_attribute.attr, NULL, /* need to NULL terminate the list of attributes */ }; static struct attribute_group attr_group = { .attrs = attrs, }; #endif //DTMF_SUPPORT /* if mutual exclusion is required for your particular platform * then add mutex lock/unlock to this driver */ static void zl380twEnterCritical(struct zl380tw_priv *zl380tw) { mutex_lock(&zl380tw->hbi_lock); } static void zl380twExitCritical(struct zl380tw_priv *zl380tw) { mutex_unlock(&zl380tw->hbi_lock); } /* write up to 252 bytes data */ /* zl380tw_nbytes_wr()- rewrite one or up to 252 bytes to the device * \param[in] ptwdata pointer to the data to write * \param[in] numbytes the number of bytes to write * * return ::status = the total number of bytes transferred successfully * or a negative error code */ static int zl380tw_nbytes_wr(struct zl380tw_priv *zl380tw, u16 numbytes, u8 *pData) { int status; #ifdef MICROSEMI_HBI_SPI struct spi_message msg; struct spi_transfer xfer = { .len = numbytes, .tx_buf = pData, }; spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); status = spi_sync(zl380tw->spi, &msg); #endif #ifdef MICROSEMI_HBI_I2C status = i2c_master_send(zl380tw->i2c, pData, numbytes); #endif if (status < 0) return -EFAULT; return 0; } /* zl38040_hbi_rd16()- Decode the 16-bit T-WOLF Regs Host address into * page, offset and build the 16-bit command acordingly to the access type. * then read the 16-bit word data and store it in pdata * \param[in] * .addr the 16-bit HBI address * .pdata pointer to the data buffer read or write * * return ::status */ static int zl380tw_hbi_rd16(struct zl380tw_priv *zl380tw, u16 addr, u16 *pdata) { u16 cmd; u8 page; u8 offset; u8 i = 0; u8 buf[4] = {0, 0, 0, 0}; int status =0; #ifdef MICROSEMI_HBI_I2C struct i2c_msg msg[2]; #endif page = addr >> 8; offset = (addr & 0xFF)>>1; if (page == 0) /*Direct page access*/ { cmd = HBI_DIRECT_READ(offset, 0);/*build the cmd*/ buf[i++] = (cmd >> 8) & 0xFF; buf[i++] = (cmd & 0xFF); } else { /*indirect page access*/ if (page != 0xFF) { page -= 1; } cmd = HBI_SELECT_PAGE(page); i = 0; /*select the page*/ buf[i++] = (cmd >> 8) & 0xFF; buf[i++] = (cmd & 0xFF); cmd = HBI_PAGED_READ(offset, 0); /*build the cmd*/ buf[i++] = (cmd >> 8) & 0xFF; buf[i++] = (cmd & 0xFF); } /*perform the HBI access*/ #ifdef MICROSEMI_HBI_SPI status = spi_write_then_read(zl380tw->spi, buf, i, buf, 2); #endif #ifdef MICROSEMI_HBI_I2C memset(msg,0,sizeof(msg)); /* Make write msg 1st with write command */ msg[0].addr = zl380tw->i2c->addr; msg[0].len = i; msg[0].buf = buf; // TW_DEBUG2("i2c addr = 0x%04x\n", msg[0].addr); // TW_DEBUG2("numbytes:%d, cmdword = 0x%02x, %02x\n", numbytes, msg[0].buf[0], msg[0].buf[1]); /* Make read msg */ msg[1].addr = zl380tw->i2c->addr; msg[1].len = 2; msg[1].buf = buf; msg[1].flags = I2C_M_RD; status = i2c_transfer(zl380tw->i2c->adapter, msg, 2); #endif if (status <0) return status; *pdata = (buf[0]<<8) | buf[1] ; /* Byte_HI, Byte_LO */ return 0; } /* zl38040_hbi_wr16()- this function is used for single word access by the * ioctl read. It decodes the 16-bit T-WOLF Regs Host address into * page, offset and build the 16-bit command acordingly to the access type. * then write the command and data to the device * \param[in] * .addr the 16-bit HBI address * .data the 16-bit word to write * * return ::status */ static int zl380tw_hbi_wr16(struct zl380tw_priv *zl380tw, u16 addr, u16 data) { u16 cmd; u8 page; u8 offset; u8 i =0; u8 buf[6] = {0, 0, 0, 0, 0, 0}; int status =0; page = addr >> 8; offset = (addr & 0xFF)>>1; if (page == 0) /*Direct page access*/ { cmd = HBI_DIRECT_WRITE(offset, 0);/*build the cmd*/ buf[i++] = (cmd >> 8) & 0xFF; buf[i++] = (cmd & 0xFF); } else { /*indirect page access*/ if (page != 0xFF) { page -= 1; } cmd = HBI_SELECT_PAGE(page); i = 0; /*select the page*/ buf[i++] = (cmd >> 8) & 0xFF; buf[i++] = (cmd & 0xFF); cmd = HBI_PAGED_WRITE(offset, 0); /*build the cmd*/ buf[i++] = (cmd >> 8) & 0xFF; buf[i++] = (cmd & 0xFF); } buf[i++] = (data >> 8) & 0xFF ; buf[i++] = (data & 0xFF) ; status = zl380tw_nbytes_wr(zl380tw, i, buf); if (status <0) return status; return 0; } /*poll a specific bit of a register for clearance * [paran in] addr: the 16-bit register to poll * [paran in] bit: the bit position (0-15) to poll * [paran in] timeout: the if bit is not cleared within timeout exit loop */ static int zl380tw_monitor_bit(struct zl380tw_priv *zl380tw, u16 addr, u8 bit, u16 timeout) { u16 data = 0xBAD; do { zl380tw_hbi_rd16(zl380tw, addr, &data); msleep(10); } while ((((data & (1 << bit))>>bit) > 0) && (timeout-- >0)); if (timeout <= 0) { TW_DEBUG1(" Operation Mode, in timeout = %d \n", timeout); return -1; } return 0; } /* zl380tw_reset(): use this function to reset the device. * * * Input Argument: mode - the supported reset modes: * VPROC_ZL38040_RST_HARDWARE_ROM, * VPROC_ZL38040_RST_HARDWARE_ROM, * VPROC_ZL38040_RST_SOFT, * VPROC_ZL38040_RST_AEC * VPROC_ZL38040_RST_TO_BOOT * Return: type error code (0 = success, else= fail) */ static int zl380tw_reset(struct zl380tw_priv *zl380tw, u16 mode) { u16 addr = CLK_STATUS_REG; u16 data = 0; int monitor_bit = -1; /*the bit (range 0 - 15) of that register to monitor*/ /*PLATFORM SPECIFIC code*/ if (mode == ZL38040_RST_HARDWARE_RAM) { /*hard reset*/ /*hard reset*/ data = 0x0005; } else if (mode == ZL38040_RST_HARDWARE_ROM) { /*power on reset*/ /*hard reset*/ data = 0x0009; } else if (mode == ZL38040_RST_AEC) { /*AEC method*/ addr = 0x0300; data = 0x0001; monitor_bit = 0; } else if (mode == ZL38040_RST_SOFTWARE) { /*soft reset*/ addr = 0x0006; data = 0x0002; monitor_bit = 1; } else if (mode == ZL38040_RST_TO_BOOT) { /*reset to bootrom mode*/ data = 0x0001; } else { TW_DEBUG1("Invalid reset type\n"); return -EINVAL; } if (zl380tw_hbi_wr16(zl380tw, addr, data) < 0) return -EFAULT; msleep(50); /*wait for the HBI to settle*/ if (monitor_bit >= 0) { if (zl380tw_monitor_bit(zl380tw, addr, monitor_bit, 1000) < 0) return -EFAULT; } return 0; } /* tw_mbox_acquire(): use this function to * check whether the host or the device owns the mailbox right * * Input Argument: None * Return: error code (0 = success, else= fail) */ static int zl380tw_mbox_acquire(struct zl380tw_priv *zl380tw) { int status =0; /*Check whether the host owns the command register*/ u16 i=0; u16 temp = 0x0BAD; for (i = 0; i < TWOLF_MBCMDREG_SPINWAIT; i++) { status = zl380tw_hbi_rd16(zl380tw, ZL38040_SW_FLAGS_REG, &temp); if ((status < 0)) { TW_DEBUG1("ERROR %d: \n", status); return status; } if (!(temp & ZL38040_SW_FLAGS_CMD)) {break;} msleep(10); /*release*/ TW_DEBUG2("cmdbox =0x%04x timeout count = %d: \n", temp, i); } TW_DEBUG2("timeout count = %d: \n", i); if ((i>= TWOLF_MBCMDREG_SPINWAIT) && (temp != ZL38040_SW_FLAGS_CMD)) { return -EBUSY; } /*read the Host Command register*/ return 0; } /* zl380tw_cmdreg_acquire(): use this function to * check whether the last command is completed * * Input Argument: None * Return: error code (0 = success, else= fail) */ static int zl380tw_cmdreg_acquire(struct zl380tw_priv *zl380tw) { int status =0; /*Check whether the host owns the command register*/ u16 i=0; u16 temp = 0x0BAD; for (i = 0; i < TWOLF_MBCMDREG_SPINWAIT; i++) { status = zl380tw_hbi_rd16(zl380tw, ZL38040_CMD_REG, &temp); if ((status < 0)) { TW_DEBUG1("ERROR %d: \n", status); return status; } if (temp == ZL38040_CMD_IDLE) {break;} msleep(10); /*wait*/ TW_DEBUG2("cmdReg =0x%04x timeout count = %d: \n", temp, i); } TW_DEBUG2("timeout count = %d: \n", i); if ((i>= TWOLF_MBCMDREG_SPINWAIT) && (temp != ZL38040_CMD_IDLE)) { return -EBUSY; } /*read the Host Command register*/ return 0; } /* zl380tw_write_cmdreg(): use this function to * access the host command register (mailbox access type) * * Input Argument: cmd - the command to send * Return: error code (0 = success, else= fail) */ static int zl380tw_write_cmdreg(struct zl380tw_priv *zl380tw, u16 cmd) { int status = 0; u16 buf = cmd; /*Check whether the host owns the command register*/ status = zl380tw_mbox_acquire(zl380tw); if ((status < 0)) { TW_DEBUG1("ERROR %d: \n", status); return status; } /*write the command into the Host Command register*/ status = zl380tw_hbi_wr16(zl380tw, ZL38040_CMD_REG, buf); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } /*Release the command reg*/ buf = ZL38040_SW_FLAGS_CMD; status = zl380tw_hbi_wr16(zl380tw, ZL38040_SW_FLAGS_REG, buf); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } return zl380tw_cmdreg_acquire(zl380tw); } /* Write 16bit HBI Register */ /* zl380tw_wr16()- write a 16bit word * \param[in] cmdword the 16-bit word to write * * return ::status */ static int zl380tw_wr16(struct zl380tw_priv *zl380tw, u16 cmdword) { u8 buf[2] = {(cmdword >> 8) & 0xFF, (cmdword & 0xFF)}; int status = 0; #ifdef MICROSEMI_HBI_SPI struct spi_message msg; struct spi_transfer xfer = { .len = 2, .tx_buf = buf, }; spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); status = spi_sync(zl380tw->spi, &msg); #endif #ifdef MICROSEMI_HBI_I2C status = i2c_master_send(zl380tw->i2c, buf, 2); #endif return status; } /*To initialize the Twolf HBI interface * Param[in] - cmd_config : the 16-bit HBI init command ored with the * 8-bit configuration. * The command format is cmd_config = 0xFD00 | CONFIG_VAL * CONFIG_VAL: is you desired HBI config value * (see device datasheet) */ static int zl380tw_hbi_init(struct zl380tw_priv *zl380tw, u16 cmd_config) { return zl380tw_wr16(zl380tw, HBI_CONFIGURE(cmd_config)); } #if defined(ZL380XX_TW_ENABLE_CHAR_DEV_DRIVER) || defined(ZL380XX_TW_UPDATE_FIRMWARE) static int zl380tw_cmdresult_check(struct zl380tw_priv *zl380tw) { int status = 0; u16 buf; status = zl380tw_hbi_rd16(zl380tw, ZL38040_CMD_PARAM_RESULT_REG, &buf); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } if (buf !=0) { TW_DEBUG1("Command failed...Resultcode = 0x%04x\n", buf); return buf; } return 0; } /*stop_fwr_to_bootmode - use this function to stop the firmware currently *running * And set the device in boot mode * \param[in] none * * \retval ::0 success * \retval ::-EINVAL or device error code */ static int zl380tw_stop_fwr_to_bootmode(struct zl380tw_priv *zl380tw) { return zl380tw_write_cmdreg(zl380tw, ZL38040_CMD_FWR_STOP); } /*start_fwr_from_ram - use this function to start/restart the firmware * previously stopped with VprocTwolfFirmwareStop() * \param[in] none * * \retval ::0 success * \retval ::-EINVAL or device error code */ static int zl380tw_start_fwr_from_ram(struct zl380tw_priv *zl380tw) { return zl380tw_write_cmdreg(zl380tw, ZL38040_CMD_FWR_GO); } /*tw_init_check_flash - use this function to check if there is a flash on board * and initialize it * \param[in] none * * \retval ::0 success * \retval ::-EINVAL or device error code */ static int zl380tw_init_check_flash(struct zl380tw_priv *zl380tw) { /*if there is a flash on board initialize it*/ return zl380tw_write_cmdreg(zl380tw, ZL38040_CMD_HOST_FLASH_INIT); } /*tw_erase_flash - use this function to erase all firmware image * and related config from flash * previously stopped with VprocTwolfFirmwareStop() * \param[in] none * * \retval ::0 success * \retval ::-EINVAL or device error code */ static int zl380tw_erase_flash(struct zl380tw_priv *zl380tw) { int status =0; /*if there is a flash on board initialize it*/ status = zl380tw_init_check_flash(zl380tw); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } /*erase all config/fwr*/ status = zl380tw_hbi_wr16(zl380tw, ZL38040_CMD_PARAM_RESULT_REG, 0xAA55); if (status < 0) { return status; } /*erase firmware*/ return zl380tw_write_cmdreg(zl380tw, 0x0009); } /*erase_fwrcfg_from_flash - use this function to erase a psecific firmware image * and related config from flash * previously stopped with VprocTwolfFirmwareStop() * Input Argument: image_number - (range 1-14) * * \retval ::0 success * \retval ::-EINVAL or device error code */ static int zl380tw_erase_fwrcfg_from_flash(struct zl380tw_priv *zl380tw, u16 image_number) { int status = 0; if (image_number <= 0) { return -EINVAL; } /*save the config/fwr to flash position image_number */ status = zl380tw_stop_fwr_to_bootmode(zl380tw); if (status < 0) { return status; } msleep(50); /*if there is a flash on board initialize it*/ status = zl380tw_init_check_flash(zl380tw); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } status = zl380tw_hbi_wr16(zl380tw, ZL38040_CMD_PARAM_RESULT_REG, image_number); if (status < 0) { return status; } status = zl380tw_write_cmdreg(zl380tw, ZL38040_CMD_IMG_CFG_ERASE); if (status < 0) { return status; } return zl380tw_cmdresult_check(zl380tw); } /* tw_save_image_to_flash(): use this function to * save both the config record and the firmware to flash. It Sets the bit * which initiates a firmware save to flash when the device * moves from a STOPPED state to a RUNNING state (by using the GO bit) * * Input Argument: None * \retval ::0 success * \retval ::-EINVAL or device error code */ static int zl380tw_save_image_to_flash(struct zl380tw_priv *zl380tw) { int status = 0; /*Save firmware to flash*/ /*delete all applications on flash*/ status = zl380tw_erase_flash(zl380tw); if (status < 0) { TW_DEBUG1("ERROR %d: tw_erase_flash\n", status); return status; } /*save the image to flash*/ status = zl380tw_write_cmdreg(zl380tw, ZL38040_CMD_IMG_CFG_SAVE); if (status < 0) { TW_DEBUG1("ERROR %d: zl380tw_write_cmdreg\n", status); return status; } return zl380tw_cmdresult_check(zl380tw); /*return status;*/ } /*load_fwr_from_flash - use this function to load a specific firmware image * from flash * previously stopped with VprocTwolfFirmwareStop() * Input Argument: image_number - (range 1-14) * * \retval ::0 success * \retval ::-EINVAL or device error code */ static int zl380tw_load_fwr_from_flash(struct zl380tw_priv *zl380tw, u16 image_number) { int status = 0; if (image_number <= 0) { return -EINVAL; } /*if there is a flash on board initialize it*/ status = zl380tw_init_check_flash(zl380tw); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } /*load the fwr to flash position image_number */ status = zl380tw_hbi_wr16(zl380tw, ZL38040_CMD_PARAM_RESULT_REG, image_number); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } return zl380tw_write_cmdreg(zl380tw, ZL38040_CMD_IMG_LOAD); } /*zl380tw_load_fwrcfg_from_flash - use this function to load a specific firmware image * related and config from flash * previously stopped with VprocTwolfFirmwareStop() * Input Argument: image_number - (range 1-14) * * \retval ::0 success * \retval ::-EINVAL or device error code */ static int zl380tw_load_fwrcfg_from_flash(struct zl380tw_priv *zl380tw, u16 image_number) { int status = 0; if (image_number <= 0) { return -EINVAL; } /*if there is a flash on board initialize it*/ status = zl380tw_init_check_flash(zl380tw); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } /*save the config to flash position image_number */ status = zl380tw_hbi_wr16(zl380tw, ZL38040_CMD_PARAM_RESULT_REG, image_number); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } return zl380tw_write_cmdreg(zl380tw, ZL38040_CMD_IMG_CFG_LOAD); } /*zl380tw_load_cfg_from_flash - use this function to load a specific firmware image * from flash * * Input Argument: image_number - (range 1-14) * * \retval ::0 success * \retval ::-EINVAL or device error code */ static int zl380tw_load_cfg_from_flash(struct zl380tw_priv *zl380tw, u16 image_number) { int status = 0; if (image_number <= 0) { return -EINVAL; } /*if there is a flash on board initialize it*/ status = zl380tw_init_check_flash(zl380tw); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } /*load the config from flash position image_number */ status = zl380tw_hbi_wr16(zl380tw, ZL38040_CMD_PARAM_RESULT_REG, image_number); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } return zl380tw_write_cmdreg(zl380tw, ZL38040_CMD_CFG_LOAD); } /* save_cfg_to_flash(): use this function to * save the config record to flash. It Sets the bit * which initiates a config save to flash when the device * moves from a STOPPED state to a RUNNING state (by using the GO bit) * * \retval ::0 success * \retval ::-EINVAL or device error code */ static int zl380tw_save_cfg_to_flash(struct zl380tw_priv *zl380tw) { int status = 0; u16 buf = 0; /*Save config to flash*/ /*if there is a flash on board initialize it*/ status = zl380tw_init_check_flash(zl380tw); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } /*check if a firmware exists on the flash*/ status = zl380tw_hbi_rd16(zl380tw, ZL38040_FWR_COUNT_REG, &buf); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } if (buf < 0) return -EBADSLT; /*no firmware on flash to save config for*/ /*save the config to flash position */ status = zl380tw_hbi_wr16(zl380tw, ZL38040_CMD_PARAM_RESULT_REG, 0x0001); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } /*save the config to flash position */ status = zl380tw_hbi_wr16(zl380tw, ZL38040_CMD_REG, 0x8002); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } /*save the config to flash position */ status = zl380tw_hbi_wr16(zl380tw, ZL38040_SW_FLAGS_REG, 0x0004); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } status = zl380tw_cmdreg_acquire(zl380tw); if (status < 0) { TW_DEBUG1("ERROR %d: tw_cmdreg_acquire\n", status); return status; } /*Verify wheter the operation completed sucessfully*/ return zl380tw_cmdresult_check(zl380tw); } /*AsciiHexToHex() - to convert ascii char hex to integer hex * pram[in] - str - pointer to the char to convert. * pram[in] - len - the number of character to convert (2:u8, 4:u16, 8:u32). */ static unsigned int AsciiHexToHex(const char * str, unsigned char len) { unsigned int val = 0; char c; unsigned char i = 0; for (i = 0; i< len; i++) { c = *str++; val <<= 4; if (c >= '0' && c <= '9') { val += c & 0x0F; continue; } c &= 0xDF; if (c >= 'A' && c <= 'F') { val += (c & 0x07) + 9; continue; } return 0; } return val; } /* These 3 functions provide an alternative method to loading an *.s3 * firmware image into the device * Procedure: * 1- Call zl380tw_boot_prepare() to put the device in boot mode * 2- Call the zl380tw_boot_Write() repeatedly by passing it a pointer * to one line of the *.s3 image at a time until the full image (all lines) * are transferred to the device successfully. * When the transfer of a line is complete, this function will return the sta- * tus VPROC_STATUS_BOOT_LOADING_MORE_DATA. Then when all lines of the image * are transferred the function will return the status * VPROC_STATUS_BOOT_LOADING_CMP * 3- zl380tw_boot_conclude() to complete and verify the completion status of * the image boot loading process * */ static int zl380tw_boot_prepare(struct zl380tw_priv *zl380tw) { u16 buf = 0; int status = 0; status = zl380tw_hbi_wr16(zl380tw, CLK_STATUS_REG, 1); /*go to boot rom mode*/ if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } msleep(50); /*required for the reset to cmplete*/ /*check whether the device has gone into boot mode as ordered*/ status = zl380tw_hbi_rd16(zl380tw, (u16)ZL38040_CMD_PARAM_RESULT_REG, &buf); if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } if ((buf != 0xD3D3)) { TW_DEBUG1("ERROR: HBI is not accessible, cmdResultCheck = 0x%04x\n", buf); return -EFAULT; } return 0; } /*----------------------------------------------------------------------------*/ static int zl380tw_boot_conclude(struct zl380tw_priv *zl380tw) { int status = 0; status = zl380tw_write_cmdreg(zl380tw, ZL38040_CMD_HOST_LOAD_CMP); /*loading complete*/ if (status < 0) { TW_DEBUG1("ERROR %d: \n", status); return status; } /*check whether the device has gone into boot mode as ordered*/ return zl380tw_cmdresult_check(zl380tw); } /*----------------------------------------------------------------------------*/ /* Read up to 256 bytes data */ /* slave_zl380xx_nbytesrd()- read one or up to 256 bytes from the device * \param[in] ptwdata pointer to the data read * \param[in] numbytes the number of bytes to read * * return ::status = the total number of bytes received successfully * or a negative error code */ static int zl380tw_nbytes_rd(struct zl380tw_priv *zl380tw, u16 numbytes, u8 *pData, u8 hbiAddrType) { int status = 0; int tx_len = (hbiAddrType == TWOLF_HBI_PAGED) ? 4 : 2; u8 tx_buf[4] = {pData[0], pData[1], pData[2], pData[3]}; #ifdef MICROSEMI_HBI_SPI struct spi_message msg; struct spi_transfer txfer = { .len = tx_len, .tx_buf = tx_buf, }; struct spi_transfer rxfer = { .len = numbytes, .rx_buf = zl380tw->pData, }; spi_message_init(&msg); spi_message_add_tail(&txfer, &msg); spi_message_add_tail(&rxfer, &msg); status = spi_sync(zl380tw->spi, &msg); #endif #ifdef MICROSEMI_HBI_I2C struct i2c_msg msg[2]; memset(msg,0,sizeof(msg)); msg[0].addr = zl380tw->i2c->addr; msg[0].len = tx_len; msg[0].buf = tx_buf; msg[1].addr = zl380tw->i2c->addr; msg[1].len = numbytes; msg[1].buf = zl380tw->pData; msg[1].flags = I2C_M_RD; status = i2c_transfer(zl380tw->i2c->adapter, msg, 2); #endif if (status <0) return status; #ifdef MSCCDEBUG2 { int i = 0; printk("RD: Numbytes = %d, addrType = %d\n", numbytes, hbiAddrType); for(i=0;ipData[i]); } printk("\n"); } #endif return 0; } static int zl380tw_hbi_access(struct zl380tw_priv *zl380tw, u16 addr, u16 numbytes, u8 *pData, u8 hbiAccessType) { u16 cmd; u8 page = addr >> 8; u8 offset = (addr & 0xFF)>>1; int i = 0; u8 hbiAddrType = 0; u8 buf[MAX_TWOLF_ACCESS_SIZE_IN_BYTES]; int status = 0; u8 numwords = (numbytes/2); #ifdef MICROSEMI_HBI_SPI if (zl380tw->spi == NULL) { TW_DEBUG1("spi device is not available \n"); return -EFAULT; } #endif #ifdef MICROSEMI_HBI_I2C if (zl380tw->i2c == NULL) { TW_DEBUG1("i2c device is not available \n"); return -EFAULT; } #endif if ((numbytes > MAX_TWOLF_ACCESS_SIZE_IN_BYTES) || (numbytes ==0)) return -EINVAL; if (pData == NULL) return -EINVAL; if (!((hbiAccessType == TWOLF_HBI_WRITE) || (hbiAccessType == TWOLF_HBI_READ))) return -EINVAL; if (page == 0) /*Direct page access*/ { if (hbiAccessType == TWOLF_HBI_WRITE) cmd = HBI_DIRECT_WRITE(offset, numwords-1);/*build the cmd*/ else cmd = HBI_DIRECT_READ(offset, numwords-1);/*build the cmd*/ buf[i++] = (cmd >> 8) & 0xFF ; buf[i++] = (cmd & 0xFF) ; hbiAddrType = TWOLF_HBI_DIRECT; } else { /*indirect page access*/ i = 0; if (page != 0xFF) { page -= 1; } cmd = HBI_SELECT_PAGE(page); /*select the page*/ buf[i++] = (cmd >> 8) & 0xFF ; buf[i++] = (cmd & 0xFF) ; /*address on the page to access*/ if (hbiAccessType == TWOLF_HBI_WRITE) cmd = HBI_PAGED_WRITE(offset, numwords-1); else cmd = HBI_PAGED_READ(offset, numwords-1);/*build the cmd*/ buf[i++] = (cmd >> 8) & 0xFF ; buf[i++] = (cmd & 0xFF) ; hbiAddrType = TWOLF_HBI_PAGED; } memcpy(&buf[i], pData, numbytes); #ifdef MSCCDEBUG2 { int j = 0; int displaynum = numbytes; if (hbiAccessType == TWOLF_HBI_WRITE) displaynum = numbytes; else displaynum = i; printk("SENDING:: Numbytes = %d, accessType = %d\n", numbytes, hbiAccessType); for(j=0;j> 24) & 0xFF); buf[1] = (u8)((address >> 16) & 0xFF); buf[2] = (u8)((address >> 8) & 0xFF); buf[3] = 0; page255Offset = (u8)(address & 0xFF); /* store the execution address */ if ((rec_type == 7) || (rec_type == 8) || (rec_type == 9)) { /* the address is the execution address for the program */ //TW_DEBUG2("execAddr = 0x%08lx\n", address); /* program the program's execution start register */ buf[3] = (u8)(address & 0xFF); status = zl380tw_hbi_access(zl380tw, ZL38040_FWR_EXEC_REG, 4, buf, TWOLF_HBI_WRITE); if(status < 0) { TW_DEBUG1("ERROR % d: unable to program page 1 execution address\n", status); return status; } TW_DEBUG2("Loading firmware data complete...\n"); return TWOLF_STATUS_BOOT_COMPLETE; /*BOOT_COMPLETE Sucessfully*/ } /* put the address into our global target addr */ //TW_DEBUG2("TW_DEBUG2:gTargetAddr = 0x%08lx: \n", address); status = zl380tw_hbi_access(zl380tw, PAGE_255_BASE_HI_REG, 4, buf, TWOLF_HBI_WRITE); if (status < 0) { TW_DEBUG1("ERROR %d: gTargetAddr = 0x%08lx: \n", status, address); return -EFAULT; } /* get the data bytes */ j = 12; //TW_DEBUG2("buf[]= 0x%02x, 0x%02x, \n", buf[0], buf[1]); for (i = 0; i < numbytesPerLine - 5; i++) { buf[i] = AsciiHexToHex(&blockOfFwrData[j], 2); j +=2; //TW_DEBUG2("0x%02x, ", buf[i+4]); } /* write the data to the device */ cmd = (u16)(0xFF<<8) | (u16)page255Offset; status = zl380tw_hbi_access(zl380tw, cmd, (numbytesPerLine - 5), buf, TWOLF_HBI_WRITE); if(status < 0) { return status; } //TW_DEBUG2("Provide next block of data...\n"); return TWOLF_STATUS_NEED_MORE_DATA; /*REQUEST STATUS_MORE_DATA*/ } #endif /*----------------------------------------------------------------------* * The kernel driver functions are defined below *-------------------------DRIVER FUNCTIONs-----------------------------*/ /* zl380tw_ldfwr() * This function basically will load the firmware into the Timberwolf device * at power up. this is convenient for host pluging system that does not have * a slave EEPROM/FLASH to store the firmware, therefore, and that * requires the device to be fully operational at power up */ #ifdef ZL380XX_TW_UPDATE_FIRMWARE static int zl380tw_ldfwr(struct zl380tw_priv *zl380tw, const char * firmware_name) { int status = 0; const struct firmware *twfw; u8 numbytesPerLine = 0; u32 j =0; u8 block_size = 0; zl380twEnterCritical(zl380tw); status = request_firmware(&twfw, firmware_name, #ifdef MICROSEMI_HBI_SPI &zl380tw->spi->dev #endif #ifdef MICROSEMI_HBI_I2C &zl380tw->i2c->dev #endif ); if (status) { TW_DEBUG1("err %d, request_firmware failed to load %s\n", status, firmware_name); zl380twExitCritical(zl380tw); return -EINVAL; } /*check validity of the S-record firmware file*/ if (twfw->data[0] != 'S') { TW_DEBUG1("Invalid S-record %s file for this device\n", firmware_name); release_firmware(twfw); zl380twExitCritical(zl380tw); return -EINVAL; } zl38040->pData = kzalloc(MAX_TWOLF_FIRMWARE_SIZE_IN_BYTES, GFP_KERNEL); if (zl38040->pData == NULL) { TW_DEBUG1("can't allocate memory\n"); release_firmware(twfw); zl380twExitCritical(zl380tw); return -ENOMEM; } status = zl380tw_boot_prepare(zl380tw); if (status < 0) { TW_DEBUG1("err %d, tw boot prepare failed\n", status); goto fwr_cleanup; } do { numbytesPerLine = AsciiHexToHex(&twfw->data[j+2], 2); block_size = (4 + (2*numbytesPerLine)); memcpy(zl380tw->pData, &twfw->data[j], block_size); j += (block_size+2); status = zl380tw_boot_Write(zl380tw, zl380tw->pData); if ((status != (int)TWOLF_STATUS_NEED_MORE_DATA) && (status != (int)TWOLF_STATUS_BOOT_COMPLETE)) { TW_DEBUG1(KERN_ERR "err %d, tw boot write failed\n", status); goto fwr_cleanup; } } while ((j < twfw->size) && (status != TWOLF_STATUS_BOOT_COMPLETE)); status = zl380tw_boot_conclude(zl380tw); if (status < 0) { TW_DEBUG1("err %d, twfw->size = %d, tw boot conclude -firmware loading failed\n", status, twfw->size); goto fwr_cleanup; } #ifdef ZL38040_SAVE_FWR_TO_FLASH status = zl380tw_save_image_to_flash(zl380tw); if (status < 0) { TW_DEBUG1("err %d, twfw->size = %d, saving firmware failed\n", status, twfw->size); goto fwr_cleanup; } #endif /*ZL38040_SAVE_FWR_TO_FLASH*/ status = zl380tw_start_fwr_from_ram(zl380tw); if (status < 0) { TW_DEBUG1("err %d, twfw->size = %d, starting firmware failed\n", status, twfw->size); goto fwr_cleanup; } goto fwr_cleanup; fwr_cleanup: zl380twExitCritical(zl380tw); release_firmware(twfw); kfree((void *)zl380tw->pData); zl380tw->pData = NULL; return status; } #endif /*-------------------------------------------------------------------- * ALSA SOC CODEC driver *--------------------------------------------------------------------*/ #ifdef ZL380XX_TW_ENABLE_ALSA_CODEC_DRIVER /* ALSA soc codec default Timberwolf register settings * 3 Example audio cross-point configurations */ #ifdef MICROSEMI_DEMO_PLATFORM /*pure stereo bypass with no AEC * Record * MIC1 -> I2S-L * MIC2 -> I2S-R * Playback * I2S-L -> DAC1 * I2S-R -> DAC2 *reg 0x202 - 0x226 */ #define CODEC_CONFIG_REG_NUM 19 u16 reg_stereo[] ={0x000F, 0x0010, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0005, 0x0006, 0x0001, 0x0002, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}; /*4-port mode with AEC enabled - ROUT to DAC1 * MIC1 -> SIN (ADC) * I2S-L -> RIN (TDM Rx path) * SOUT -> I2S-L * ROUT -> DACx *reg 0x202 - 0x226 */ u16 reg_aec[] ={0x0c05, 0x0010, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x000d, 0x0000, 0x000e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x000d, 0x0001, 0x0005}; /*Loopback mode ADC to DAC1 * MIC1 -> DAC1 *reg 0x202 - 0x226 */ u16 reg_loopback[] ={0x003, 0x0010, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x000d, 0x0000, 0x0000}; #endif /* * * Formatting for the Audio * * */ #define zl380tw_DAI_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000) #define zl380tw_DAI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) #define zl380tw_DAI_CHANNEL_MIN 1 #define zl380tw_DAI_CHANNEL_MAX 2 static unsigned int zl380tw_reg_read(struct snd_soc_codec *codec, unsigned int reg) { unsigned int value = 0; struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); u16 buf; if (zl380tw_hbi_rd16(zl380tw, reg, &buf) < 0) { return -EIO; } value = buf; return value; } static int zl380tw_reg_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); if (zl380tw_hbi_wr16(zl380tw, reg, value) < 0) { return -EIO; } return 0; } /*ALSA- soc codec I2C/SPI read control functions*/ static int zl380tw_control_read(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; unsigned int reg = mc->reg; unsigned int shift = mc->shift; unsigned int mask = mc->max; unsigned int invert = mc->invert; u16 val; dump_stack(); zl380twEnterCritical(zl380tw); if (zl380tw_hbi_rd16(zl380tw, reg, &val) < 0) { dump_stack(); zl380twExitCritical(zl380tw); return -EIO; } zl380twExitCritical(zl380tw); ucontrol->value.integer.value[0] = ((val >> shift) & mask); if (invert) ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; return 0; } /*ALSA- soc codec I2C/SPI write control functions*/ static int zl380tw_control_write(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; unsigned int reg = mc->reg; unsigned int shift = mc->shift; unsigned int mask = mc->max; unsigned int invert = mc->invert; unsigned int val = (ucontrol->value.integer.value[0] & mask); u16 valt; if (invert) val = mask - val; zl380twEnterCritical(zl380tw); if (zl380tw_hbi_rd16(zl380tw, reg, &valt) < 0) { zl380twExitCritical(zl380tw); return -EIO; } if (((valt >> shift) & mask) == val) { zl380twExitCritical(zl380tw); return 0; } valt &= ~(mask << shift); valt |= val << shift; if (zl380tw_reg_write(codec, reg, valt) < 0) { zl380twExitCritical(zl380tw); return -EIO; } zl380twExitCritical(zl380tw); return 0; } int zl380tw_mute_r(struct snd_soc_codec *codec, int on) { struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); u16 val; zl380twEnterCritical(zl380tw); if (zl380tw_hbi_rd16(zl380tw, ZL38040_AEC_CTRL_REG0, &val) < 0){ zl380twExitCritical(zl380tw); return -EIO; } if (((val >> 7) & 1) == on){ zl380twExitCritical(zl380tw); return 0; } val &= ~(1 << 7); val |= on << 7; if (zl380tw_reg_write(codec, ZL38040_AEC_CTRL_REG0, val) < 0){ zl380twExitCritical(zl380tw); return -EIO; } zl380twExitCritical(zl380tw); return 0; } int zl380tw_mute_s(struct snd_soc_codec *codec, int on) { u16 val; struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); zl380twEnterCritical(zl380tw); if (zl380tw_hbi_rd16(zl380tw, ZL38040_AEC_CTRL_REG0, &val) < 0){ zl380twExitCritical(zl380tw); return -EIO; } if (((val >> 8) & 1) == on){ zl380twExitCritical(zl380tw); return 0; } val &= ~(1 << 8); val |= on << 8; if (zl380tw_reg_write(codec, ZL38040_AEC_CTRL_REG0, val) < 0){ zl380twExitCritical(zl380tw); return -EIO; } zl380twExitCritical(zl380tw); return 0; } #ifdef MICROSEMI_DEMO_PLATFORM /* configure_codec() - configure the cross-point to either pure 2-channel stereo * or for 4-port mode with Achoustic Echo Canceller * mode: 0 -> Stereo bypass * 1 -> 4-port mode AEC * 2 -> */ static int zl380tw_configure_codec(struct snd_soc_codec *codec, u8 mode) { struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); u16 *pData; u8 mic_en = 1; /*Enable just MIC1*/ int status = 0, i; u16 dac1 =0x0000, dac2 =0x0000; if (zl380tw_hbi_rd16(zl380tw, ZL38040_DAC1_EN_REG, &dac1) < 0){ return -EFAULT; } switch (mode) { case ZL38040_SINGLE_CHANNEL_AEC: pData = reg_aec; dac1 |= (ZL38040_DACx_P_EN | ZL38040_DACx_M_EN); break; case ZL38040_STEREO_BYPASS: pData = reg_stereo; mic_en = 3; /*Enable MIC1 and MIC2 for stereo recording*/ if (zl380tw_hbi_rd16(zl380tw, ZL38040_DAC2_EN_REG, &dac2) < 0){ return -EFAULT; } dac1 |= (ZL38040_DACx_P_EN | ZL38040_DACx_M_EN); dac2 |= (ZL38040_DACx_P_EN | ZL38040_DACx_M_EN); break; case ZL38040_ADDA_LOOPBACK: pData = reg_loopback; dac1 |= (ZL38040_DACx_P_EN | ZL38040_DACx_M_EN); break; default: { return -EINVAL; } } for (i = 0; i <= CODEC_CONFIG_REG_NUM; i++) { if (zl380tw_hbi_wr16(zl380tw, ZL38040_CACHED_ADDR_LO +(2*i), pData[i]) < 0) return -1; } if (zl380tw_reg_write(codec, ZL38040_MIC_EN_REG, (u16)mic_en) < 0) return -1; if (zl380tw_reg_write(codec, ZL38040_DAC1_EN_REG, dac1) < 0) return -1; if (zl380tw_reg_write(codec, ZL38040_DAC2_EN_REG, dac2) < 0) return -1; //dev_info(codec->dev, "mode= %d: dac1=0x%04x, dac2=0x%04x, mic=0x%02x\n", mode, dac1, dac2, mic_en); status = zl380tw_reset(zl380tw, ZL38040_RST_SOFTWARE); /*soft-reset*/ if (status < 0) { return status; } return 0; } #endif /*The DACx, I2Sx, TDMx Gains can be used in both AEC mode or Stereo bypass mode * however the AEC Gains can only be used when AEC is active. * Each input source of the cross-points has two input sources A and B. * The Gain for each source can be controlled independantly. */ static const struct snd_kcontrol_new zl380tw_snd_controls[] = { SOC_SINGLE_EXT("DAC1 GAIN INA", ZL38040_DAC1_GAIN_REG, 0, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("DAC2 GAIN INA", ZL38040_DAC2_GAIN_REG, 0, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("I2S1L GAIN INA", ZL38040_I2S1L_GAIN_REG, 0, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("I2S1R GAIN INA", ZL38040_I2S1R_GAIN_REG, 0, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("I2S2L GAIN INA", ZL38040_I2S2L_GAIN_REG, 0, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("I2S2R GAIN INA", ZL38040_I2S2R_GAIN_REG, 0, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("TDMA3 GAIN INA", ZL38040_TDMA3_GAIN_REG, 0, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("TDMA4 GAIN INA", ZL38040_TDMA4_GAIN_REG, 0, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("TDMB3 GAIN INA", ZL38040_TDMB3_GAIN_REG, 0, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("TDMB4 GAIN INA", ZL38040_TDMB4_GAIN_REG, 0, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("DAC1 GAIN INB", ZL38040_DAC1_GAIN_REG, 8, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("DAC2 GAIN INB", ZL38040_DAC2_GAIN_REG, 8, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("I2S1L GAIN INB", ZL38040_I2S1L_GAIN_REG, 8, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("I2S1R GAIN INB", ZL38040_I2S1R_GAIN_REG, 8, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("I2S2L GAIN INB", ZL38040_I2S2L_GAIN_REG, 8, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("I2S2R GAIN INB", ZL38040_I2S2R_GAIN_REG, 8, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("TDMA3 GAIN INB", ZL38040_TDMA3_GAIN_REG, 8, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("TDMA4 GAIN INB", ZL38040_TDMA4_GAIN_REG, 8, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("TDMB3 GAIN INB", ZL38040_TDMB3_GAIN_REG, 8, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("TDMB4 GAIN INB", ZL38040_TDMB4_GAIN_REG, 8, 0x6, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("AEC ROUT GAIN", ZL38040_USRGAIN, 0, 0x78, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("AEC ROUT GAIN EXT", ZL38040_USRGAIN, 7, 0x7, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("AEC SOUT GAIN", ZL38040_SYSGAIN, 8, 0xf, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("AEC MIC GAIN", ZL38040_SYSGAIN, 0, 0xff, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("MUTE SPEAKER ROUT", ZL38040_AEC_CTRL_REG0, 7, 1, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("MUTE MIC SOUT", ZL38040_AEC_CTRL_REG0, 8, 1, 0, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("AEC Bypass", ZL38040_AEC_CTRL_REG0, 4, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("AEC Audio enh bypass", ZL38040_AEC_CTRL_REG0, 5, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("AEC Master Bypass", ZL38040_AEC_CTRL_REG0, 1, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("ALC GAIN", ZL38040_AEC_CTRL_REG1, 12, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("ALC Disable", ZL38040_AEC_CTRL_REG1, 10, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("AEC Tail disable", ZL38040_AEC_CTRL_REG1, 12, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("AEC Comfort Noise", ZL38040_AEC_CTRL_REG1, 6, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("NLAEC Disable", ZL38040_AEC_CTRL_REG1, 14, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("AEC Adaptation", ZL38040_AEC_CTRL_REG1, 1, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("NLP Disable", ZL38040_AEC_CTRL_REG1, 5, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("Noise Inject", ZL38040_AEC_CTRL_REG1, 6, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("LEC Disable", ZL38040_LEC_CTRL_REG, 4, 1, 1, zl380tw_control_read, zl380tw_control_write), SOC_SINGLE_EXT("LEC Adaptation", ZL38040_LEC_CTRL_REG, 1, 1, 1, zl380tw_control_read, zl380tw_control_write), }; int zl380tw_add_controls(struct snd_soc_codec *codec) { #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)) return snd_soc_add_controls(codec, zl380tw_snd_controls, ARRAY_SIZE(zl380tw_snd_controls)); #else return snd_soc_add_codec_controls(codec, zl380tw_snd_controls, ARRAY_SIZE(zl380tw_snd_controls)); #endif } static int zl380tw_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { #ifdef MICROSEMI_DEMO_PLATFORM struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec; struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); unsigned int buf2, buf3, buf4; unsigned int buf0 = zl380tw_reg_read(codec, ZL38040_TDMA_CLK_CFG_REG); zl380twEnterCritical(zl380tw); buf2 = zl380tw_reg_read(codec, ZL38040_TDMA_CH1_CFG_REG); buf3 = zl380tw_reg_read(codec, ZL38040_TDMA_CH2_CFG_REG); buf4 = zl380tw_reg_read(codec, ZL38040_AEC_CTRL_REG0); if ((buf2 == 0x0BAD) || (buf3 == 0x0BAD)) { zl380twExitCritical(zl380tw); return -1; } switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: buf2 |= (ZL38040_TDMA_16BIT_LIN); buf3 |= (ZL38040_TDMA_16BIT_LIN); buf3 |= 2; /*If PCM use 2 timeslots*/ break; case SNDRV_PCM_FORMAT_MU_LAW: buf2 |= (ZL38040_TDMA_8BIT_ULAW); buf3 |= (ZL38040_TDMA_8BIT_ULAW); buf3 |= 1; /*If PCM use 1 timeslots*/ break; case SNDRV_PCM_FORMAT_A_LAW: buf2 |= (ZL38040_TDMA_8BIT_ALAW ); buf3 |= (ZL38040_TDMA_8BIT_ALAW); buf3 |= 1; /*If PCM use 1 timeslots*/ break; case SNDRV_PCM_FORMAT_S20_3LE: break; case SNDRV_PCM_FORMAT_S24_LE: break; case SNDRV_PCM_FORMAT_S32_LE: break; default: { zl380twExitCritical(zl380tw); return -EINVAL; } } /* If the ZL38040 TDM is configured as a slace I2S/PCM, * The rate settings are not necessary. The Zl38040 has the ability to * auto-detect the master rate and configure itself accordingly * If configured as master, the rate must be set acccordingly */ switch (params_rate(params)) { /* For 8KHz and 16KHz sampling use a single DAC out with * AEC algorithm in 4-port mode enabled or in stere mode if AEC is disabled * For 44.1 and 48KHz, use full 2-channel stereo mode */ case 8000: case 16000: if ((buf4 & ZL38040_MASTER_BYPASS_EN) >> 1) zl380tw_configure_codec(codec, ZL38040_STEREO_BYPASS); else zl380tw_configure_codec(codec, ZL38040_SINGLE_CHANNEL_AEC); break; /*case 44100: */ case 48000: zl380tw_configure_codec(codec, ZL38040_STEREO_BYPASS); break; default: { zl380twExitCritical(zl380tw); return -EINVAL; } } zl380tw_reg_write(codec, ZL38040_TDMA_CLK_CFG_REG, buf0); zl380tw_reg_write(codec, ZL38040_TDMA_CH1_CFG_REG, buf2); zl380tw_reg_write(codec, ZL38040_TDMA_CH2_CFG_REG, buf3); zl380tw_reset(zl380tw, ZL38040_RST_SOFTWARE); zl380twExitCritical(zl380tw); #endif return 0; } static int zl380tw_mute(struct snd_soc_dai *codec_dai, int mute) { struct snd_soc_codec *codec = codec_dai->codec; /*zl380tw_mute_s(codec, mute);*/ return zl380tw_mute_r(codec, mute); } static int zl380tw_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { #ifdef MICROSEMI_DEMO_PLATFORM struct snd_soc_codec *codec = codec_dai->codec; struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); unsigned int buf, buf0; zl380twEnterCritical(zl380tw); buf = zl380tw_reg_read(codec, ZL38040_TDMA_CFG_REG); buf0 = zl380tw_reg_read(codec, ZL38040_TDMA_CLK_CFG_REG); if ((buf == 0x0BAD) || (buf0 == 0x0BAD)) { zl380twExitCritical(zl380tw); return -1; } dev_info(codec_dai->dev, "zl380tw_set_dai_fmt\n"); switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_DSP_B: break; case SND_SOC_DAIFMT_DSP_A: break; case SND_SOC_DAIFMT_RIGHT_J: if ((buf & ZL38040_TDMA_FSALIGN) >> 1) buf &= (~ZL38040_TDMA_FSALIGN); break; case SND_SOC_DAIFMT_LEFT_J: if (!((buf & ZL38040_TDMA_FSALIGN) >> 1)) buf |= (ZL38040_TDMA_FSALIGN); break; case SND_SOC_DAIFMT_I2S: if (!((buf & ZL38040_TDM_I2S_CFG_VAL) >> 1)) { buf |= (ZL38040_TDM_I2S_CFG_VAL ); } break; default: { zl380twExitCritical(zl380tw); return -EINVAL; } } /* set master/slave TDM interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: buf0 |= ZL38040_TDM_TDM_MASTER_VAL; break; case SND_SOC_DAIFMT_CBS_CFS: if ((buf0 & ZL38040_TDM_TDM_MASTER_VAL) >> 1) { buf0 &= (~ZL38040_TDM_TDM_MASTER_VAL); } break; default: { zl380twExitCritical(zl380tw); return -EINVAL; } } zl380tw_reg_write(codec, ZL38040_TDMA_CFG_REG, buf); zl380tw_reg_write(codec, ZL38040_TDMA_CLK_CFG_REG, buf0); zl380tw_reset(zl380tw, ZL38040_RST_SOFTWARE); zl380twExitCritical(zl380tw); #endif return 0; } static int zl380tw_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); zl380tw->sysclk_rate = freq; return 0; } static const struct snd_soc_dai_ops zl380tw_dai_ops = { .set_fmt = zl380tw_set_dai_fmt, .set_sysclk = zl380tw_set_dai_sysclk, .hw_params = zl380tw_hw_params, .digital_mute = zl380tw_mute, }; /*Add a snd_soc_dai_driver structure here.... for each codec devices*/ /*audio codec 0*/ static struct snd_soc_dai_driver zl380tw_dai[] = { { .name = "zl380tw0-hifi", .playback = { .stream_name = "Playback", .channels_min = zl380tw_DAI_CHANNEL_MIN, .channels_max = zl380tw_DAI_CHANNEL_MAX, .rates = zl380tw_DAI_RATES, .formats = zl380tw_DAI_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = zl380tw_DAI_CHANNEL_MIN, .channels_max = zl380tw_DAI_CHANNEL_MAX, .rates = zl380tw_DAI_RATES, .formats = zl380tw_DAI_FORMATS, }, .ops = &zl380tw_dai_ops, }, #if (NUMBER_OF_ZL380xx_DEVICES > 1) /*audio codec 1*/ { .name = "zl380tw1-hifi", .playback = { .stream_name = "Playback", .channels_min = zl380tw_DAI_CHANNEL_MIN, .channels_max = zl380tw_DAI_CHANNEL_MAX, .rates = zl380tw_DAI_RATES, .formats = zl380tw_DAI_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = zl380tw_DAI_CHANNEL_MIN, .channels_max = zl380tw_DAI_CHANNEL_MAX, .rates = zl380tw_DAI_RATES, .formats = zl380tw_DAI_FORMATS, }, .ops = &zl380tw_dai_ops, }, #endif }; static int zl380tw_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); zl380twEnterCritical(zl380tw); switch (level) { case SND_SOC_BIAS_ON: break; case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: /*wake up from sleep*/ zl380tw_hbi_init(zl380tw, (HBI_CONFIG_VAL | HBI_CONFIG_WAKE)); msleep(50); /*Clear the wake up bit*/ zl380tw_hbi_init(zl380tw, HBI_CONFIG_VAL); break; case SND_SOC_BIAS_OFF: /*Low power sleep mode*/ zl380tw_write_cmdreg(zl380tw, ZL38040_CMD_APP_SLEEP); break; } zl380twExitCritical(zl380tw); //codec->dapm.bias_level = level; return 0; } static int zl380tw_suspend(struct snd_soc_codec *codec #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)) , pm_message_t state #endif ) { struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); zl380twEnterCritical(zl380tw); zl380tw_set_bias_level(codec, SND_SOC_BIAS_OFF); zl380twExitCritical(zl380tw); return 0; } static int zl380tw_resume(struct snd_soc_codec *codec) { struct zl380tw_priv *zl380tw = snd_soc_codec_get_drvdata(codec); zl380twEnterCritical(zl380tw); zl380tw_set_bias_level(codec, SND_SOC_BIAS_STANDBY); zl380twExitCritical(zl380tw); return 0; } #ifdef DTMF_SUPPORT char decode_dtmf(int val) { char letter[10] = {'0','1','2','3','4','5','6','7','8','9'}; if(val == 0xE) return '*'; if(val == 0xF) return '#'; return letter[val]; } /** ** @brief : Gpio ISR callback function which is used to receive the data. **/ static irqreturn_t dtmf_handler(int irq, void *dev_id) { int status_add_data=0; struct snd_soc_codec *codec = (struct snd_soc_codec *)dev_id; int Val = zl380tw_reg_read(codec,ZL38040_INTR_INDI_REG); char dtmfdata=0; if(Val == EVENT_ID_DTMF ) { Val = zl380tw_reg_read(codec,ZL38040_INTR_PARA_REG); if( Val & 0x0001 ) { Val = ((Val >> 4) & 0xF); dtmfdata=decode_dtmf(Val); printk("DTMF --- %c\n",dtmfdata); dtmf.available=1; dtmf_write_local(dtmfdatabuffer,&dtmfdata,1,NULL); } } return IRQ_HANDLED; } #endif //DTMF_SUPPORT int dtmf_config(struct snd_soc_codec *codec) { #ifdef DTMF_SUPPORT int retval=0; /* Requesting GPIO for DTMF */ if(gpio_request_one(DTMF_PIN ,GPIOF_DIR_IN,"dtmf")) { printk(KERN_DEBUG"Could not set pin %d for GPIO input.\n",DTMF_PIN); return -EIO; } dtmf_irq = gpio_to_irq(DTMF_PIN); if(request_threaded_irq(dtmf_irq,NULL,dtmf_handler, IRQF_TRIGGER_FALLING|IRQF_ONESHOT, "dtmf_data",codec)) { printk(KERN_DEBUG"Can't register IRQ %d\n",DTMF_PIN); return -EIO; } //setup the sysfs audioKObj = kobject_create_and_add("audio", kernel_kobj); if (!audioKObj) { printk(KERN_ERR "audio failed to create sysfs\n"); return -ENOMEM; } retval = sysfs_create_group(audioKObj, &attr_group); if (retval) { kobject_put(audioKObj); } #endif } static int majorNumber; static int numberOpens=0; #define DEVICE_NAME "dtmf" #define CLASS_NAME "audio1" static char message[256] = {0}; ///< Memory for the string that is passed from userspace static short size_of_message; ///< Used to remember the size of the string stored static struct class* dtmfClass = NULL; ///< The device-driver class struct pointer static struct device* dtmfDevice = NULL; ///< The device-driver device struct pointer int create_dtmf_device(void); static ssize_t dtmf_write(struct file *filep, const char *buffer, size_t len, loff_t *offset); static int dtmf_open(struct inode *inodep, struct file *filep); static ssize_t dtmf_read(struct file *filep, char *buffer, size_t len, loff_t *offset); static int dtmf_release(struct inode *inodep, struct file *filep); static unsigned int dtmf_poll(struct file *filep, poll_table *wait); int dtmf_p_buffer = 4000; /* buffer size */ module_param(dtmf_p_buffer, int, 0); static struct file_operations fops_dtmf = { .open = dtmf_open, .read = dtmf_read, .write = dtmf_write, .release = dtmf_release, .poll = dtmf_poll, }; /* How much space is free? */ static int spacefree(struct dtmf_pipe *dev) { if (dev->rp == dev->wp) return dev->buffersize - 1; return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1; } /* * dtmf_poll Function * */ static unsigned int dtmf_poll(struct file *filep, poll_table *wait) { struct dtmf_pipe *dev = filep->private_data; unsigned int mask = 0; printk("pointer %p:%p\n",dev,filep); /* * The buffer is circular; it is considered full * if "wp" is right behind "rp" and empty if the * two are equal. */ down(&dev->sem); poll_wait(filep, &dev->inq, wait); poll_wait(filep, &dev->outq, wait); if (dev->rp != dev->wp) mask |= POLLIN | POLLRDNORM; /* readable */ if (spacefree(dev)) mask |= POLLOUT | POLLWRNORM; /* writable */ up(&dev->sem); return mask; } static int dtmf_p_fasync(int fd, struct file *filep, int mode) { struct dtmf_pipe *dev = filep->private_data; return fasync_helper(fd, filep, mode, &dev->async_queue); } /** @brief The device open function that is called each time the device is opened * This will only increment the numberOpens counter in this case. * @param inodep A pointer to an inode object (defined in linux/fs.h) * @param filep A pointer to a file object (defined in linux/fs.h) */ static int dtmf_open(struct inode *inodep, struct file *filep) { struct dtmf_pipe *dev; printk("open inodep = %p\n",inodep); printk("DTMF Device Opened\n"); dev = container_of(inodep->i_cdev, struct dtmf_pipe, cdev); filep->private_data = dev; dtmf_dev=dev; printk("pointer %p:%p\n",dev,filep); if (down_interruptible(&dev->sem)) return -ERESTARTSYS; if (!dev->buffer) { /* allocate the buffer */ dev->buffer = kmalloc(dtmf_p_buffer, GFP_KERNEL); if (!dev->buffer) { up(&dev->sem); return -ENOMEM; } } dev->buffersize = dtmf_p_buffer; dev->end = dev->buffer + dev->buffersize; dev->rp = dev->wp = dev->buffer; /* rd and wr from the beginning */ /* use f_mode,not f_flags: it's cleaner (fs/open.c tells why) * */ if (filep->f_mode & FMODE_READ) dev->nreaders++; if (filep->f_mode & FMODE_WRITE) dev->nwriters++; up(&dev->sem); printk(KERN_INFO "DTMF: Device has been opened\n"); return nonseekable_open(inodep, filep); numberOpens++; return 0; } /** @brief This function is called whenever device is being read from user space i.e. data is * being sent from the device to the user. In this case is uses the copy_to_user() function to * send the buffer string to the user and captures any errors. * @param filep A pointer to a file object (defined in linux/fs.h) * @param buffer The pointer to the buffer to which this function writes the data * @param len The length of the b * @param offset The offset if required */ static ssize_t dtmf_read(struct file *filep, char *buffer, size_t len, loff_t *offset){ int error_count = 0; int datalength=0; printk("DTMF Device Read Mode\n"); #if 1 struct dtmf_pipe *dev = filep->private_data; printk("pointer %p:%p\n",dev,filep); if (down_interruptible(&dev->sem)) return -ERESTARTSYS; while (dev->rp == dev->wp) { /* nothing to read */ up(&dev->sem); /* release the lock */ if (filep->f_flags & O_NONBLOCK) return -EAGAIN; //PDEBUG("\"%s\" reading: going to sleep\n", current->comm); printk("Reading: Going to Sleep\n"); if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp))) return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ /* otherwise loop, but first reacquire the lock */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; } /* ok, data is there, return something */ if (dev->wp > dev->rp) len = min(len, (size_t)(dev->wp - dev->rp)); else /* the write pointer has wrapped, return data up to dev->end */ len = min(len, (size_t)(dev->end - dev->rp)); if (copy_to_user(buffer, dev->rp, len)) { up (&dev->sem); return -EFAULT; } dev->rp += len; if (dev->rp == dev->end) dev->rp = dev->buffer; /* wrapped */ up (&dev->sem); /* finally, awake any writers and return */ wake_up_interruptible(&dev->outq); // PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count); return len; #else #endif } /* Wait for space for writing; caller must hold device semaphore. On * error the semaphore will be released before returning. */ static int scull_getwritespace(struct dtmf_pipe *dev, struct file *filep) { printk("pointer %p\n",dev); while (spacefree(dev) == 0) { /* full */ DEFINE_WAIT(wait); up(&dev->sem); if (filep->f_flags & O_NONBLOCK) return -EAGAIN; //PDEBUG("\"%s\" writing: going to sleep\n",current->comm); prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE); if (spacefree(dev) == 0) schedule(); finish_wait(&dev->outq, &wait); if (signal_pending(current)) return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; } return 0; } static int scull_getwritespace_local(struct dtmf_pipe *dev) { printk("pointer %p\n",dev); while (spacefree(dev) == 0) { /* full */ DEFINE_WAIT(wait); up(&dev->sem); //PDEBUG("\"%s\" writing: going to sleep\n",current->comm); prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE); if (spacefree(dev) == 0) schedule(); finish_wait(&dev->outq, &wait); if (signal_pending(current)) return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; } return 0; } /** @brief This function is called whenever the device is being written to from user space i.e. * data is sent to the device from the user. The data is copied to the message[] array in this * LKM using the sprintf() function along with the length of the string. * @param filep A pointer to a file object * @param buffer The buffer to that contains the string to write to the device * @param len The length of the array of data that is being passed in the const char buffer * @param offset The offset if required */ static ssize_t dtmf_write_local(struct dtmf_pipe *dev, const char *buffer, size_t count, loff_t *offset) { //struct dtmf_pipe *dev = filep->private_data; printk("pointer %p\n",dev); int result; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* Make sure there's space to write */ result = scull_getwritespace_local(dev); if (result) return result; /* scull_getwritespace called up(&dev->sem) */ /* ok, space is there, accept something */ count = min(count, (size_t)spacefree(dev)); if (dev->wp >= dev->rp) count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */ else /* the write pointer has wrapped, fill up to rp-1 */ count = min(count, (size_t)(dev->rp - dev->wp - 1)); //PDEBUG("Going to accept %li bytes to %p from %p\n", (long)count, dev->wp, buffer); if (copy_from_user(dev->wp, buffer, count)) { up (&dev->sem); return -EFAULT; } dev->wp += count; if (dev->wp == dev->end) dev->wp = dev->buffer; /* wrapped */ up(&dev->sem); /* finally, awake any reader */ wake_up_interruptible(&dev->inq); /* blocked in read() and select() */ /* and signal asynchronous readers, explained late in chapter 5 */ if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); //PDEBUG("\"%s\" did write %li bytes\n",current->comm, (long)count); return count; } /** @brief This function is called whenever the device is being written to from user space i.e. * data is sent to the device from the user. The data is copied to the message[] array in this * LKM using the sprintf() function along with the length of the string. * @param filep A pointer to a file object * @param buffer The buffer to that contains the string to write to the device * @param len The length of the array of data that is being passed in the const char buffer * @param offset The offset if required */ static ssize_t dtmf_write(struct file *filep, const char *buffer, size_t count, loff_t *offset) { struct dtmf_pipe *dev = filep->private_data; printk("pointer %p:%p\n",dev,filep); int result; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* Make sure there's space to write */ result = scull_getwritespace(dev, filep); if (result) return result; /* scull_getwritespace called up(&dev->sem) */ /* ok, space is there, accept something */ count = min(count, (size_t)spacefree(dev)); if (dev->wp >= dev->rp) count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */ else /* the write pointer has wrapped, fill up to rp-1 */ count = min(count, (size_t)(dev->rp - dev->wp - 1)); //PDEBUG("Going to accept %li bytes to %p from %p\n", (long)count, dev->wp, buffer); if (copy_from_user(dev->wp, buffer, count)) { up (&dev->sem); return -EFAULT; } dev->wp += count; if (dev->wp == dev->end) dev->wp = dev->buffer; /* wrapped */ up(&dev->sem); /* finally, awake any reader */ wake_up_interruptible(&dev->inq); /* blocked in read() and select() */ /* and signal asynchronous readers, explained late in chapter 5 */ if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); //PDEBUG("\"%s\" did write %li bytes\n",current->comm, (long)count); return count; } /** @brief The device release function that is called whenever the device is closed/released by * the userspace program * @param inodep A pointer to an inode object (defined in linux/fs.h) * @param filep A pointer to a file object (defined in linux/fs.h) */ static int dtmf_release(struct inode *inodep, struct file *filep){ struct dtmf_pipe *dev = filep->private_data; printk("pointer %p:%p\n",dev,filep); printk("DTMF Device release\n"); /* remove this filp from the asynchronously notified filp's */ dtmf_p_fasync(-1, filep, 0); down(&dev->sem); if (filep->f_mode & FMODE_READ) dev->nreaders--; if (filep->f_mode & FMODE_WRITE) dev->nwriters--; if (dev->nreaders + dev->nwriters == 0) { kfree(dev->buffer); dev->buffer = NULL; /* the other fields are not checked on open */ } up(&dev->sem); printk(KERN_INFO "EBBChar: Device successfully closed\n"); return 0; } /* * Probe Function * */ static int zl380tw_probe(struct snd_soc_codec *codec) { dev_info(codec->dev, "Probing zl380tw SoC CODEC driver\n"); printk("Probing zl380tw SoC CODEC driver\n"); /* * Create a New Device Here for Reading the DTMF tones * */ dtmf_config(codec); return zl380tw_add_controls(codec); } static int zl380tw_remove(struct snd_soc_codec *codec) { #ifdef DTMF_SUPPORT kobject_del(audioKObj); #endif //DTMF_SUPPORT return 0; } static struct snd_soc_codec_driver soc_codec_dev_zl380tw = { .probe = zl380tw_probe, .remove = zl380tw_remove, .suspend = zl380tw_suspend, .resume = zl380tw_resume, .read = zl380tw_reg_read, .write = zl380tw_reg_write, .set_bias_level = zl380tw_set_bias_level, .reg_word_size = sizeof(u16), }; #endif /*ZL380XX_TW_ENABLE_ALSA_CODEC_DRIVER*/ /*-------------------------------------------------------------------- * ALSA SOC CODEC driver - END *--------------------------------------------------------------------*/ /*-------------------------------------------------------------------- * CHARACTER type Host Interface driver *--------------------------------------------------------------------*/ #ifdef ZL380XX_TW_ENABLE_CHAR_DEV_DRIVER /* read 16bit HBI Register */ /* slave_rd16()- read a 16bit word * \param[in] pointer the to where to store the data * * return ::status */ static int zl380tw_rd16(struct zl380tw_priv *zl380tw, u16 *pdata) { u8 buf[2] = {0, 0}; int status = 0; #ifdef MICROSEMI_HBI_SPI struct spi_message msg; struct spi_transfer xfer = { .len = 2, .rx_buf = buf, }; spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); status = spi_sync(zl380tw->spi, &msg); #endif #ifdef MICROSEMI_HBI_I2C status = i2c_master_recv(zl380tw->i2c, buf, 2); #endif if (status < 0) { return status; } *pdata = (buf[0]<<8) | buf[1] ; /* Byte_HI, Byte_LO */ return 0; } static long zl380tw_io_ioctl( #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) struct inode *i, #endif struct file *filp, unsigned int cmd, unsigned long arg) { int retval =0; u16 buf =0; struct zl380tw_priv *zl380tw = filp->private_data; switch (cmd) { case TWOLF_HBI_WR16: if (copy_from_user(&zl380tw_ioctl_buf, (ioctl_zl380tw *)arg, sizeof(ioctl_zl380tw))) return -EFAULT; zl380twEnterCritical(zl380tw); retval = zl380tw_hbi_wr16(zl380tw, (u16)zl380tw_ioctl_buf.addr, zl380tw_ioctl_buf.data); zl380twExitCritical(zl380tw); break; case TWOLF_HBI_RD16: if (copy_from_user(&zl380tw_ioctl_buf, (ioctl_zl380tw *)arg, sizeof(ioctl_zl380tw))) return -EFAULT; zl380twEnterCritical(zl380tw); retval = zl380tw_hbi_rd16(zl380tw, (u16)zl380tw_ioctl_buf.addr, &zl380tw_ioctl_buf.data); zl380twExitCritical(zl380tw); if (!retval) { if (copy_to_user((ioctl_zl380tw *)arg, &zl380tw_ioctl_buf, sizeof(ioctl_zl380tw))) return -EFAULT; } else return -EAGAIN; break; case TWOLF_BOOT_PREPARE : zl380twEnterCritical(zl380tw); retval = zl380tw_boot_prepare(zl380tw); zl380twExitCritical(zl380tw); break; case TWOLF_BOOT_SEND_MORE_DATA: { if (copy_from_user(zl380tw->pData, (char *)arg, MAX_TWOLF_ACCESS_SIZE_IN_BYTES)) return -EFAULT; zl380twEnterCritical(zl380tw); retval = zl380tw_boot_Write(zl380tw, zl380tw->pData); zl380twExitCritical(zl380tw); break; } case TWOLF_BOOT_CONCLUDE : zl380twEnterCritical(zl380tw); retval = zl380tw_boot_conclude(zl380tw); zl380twExitCritical(zl380tw); break; case TWOLF_CMD_PARAM_REG_ACCESS : retval = __get_user(buf, (u16 __user *)arg); if (retval ==0) { zl380twEnterCritical(zl380tw); retval = zl380tw_write_cmdreg(zl380tw, buf); zl380twExitCritical(zl380tw); } break; case TWOLF_CMD_PARAM_RESULT_CHECK : zl380twEnterCritical(zl380tw); retval = zl380tw_cmdresult_check(zl380tw); zl380twExitCritical(zl380tw); break; case TWOLF_RESET : retval = __get_user(buf, (u16 __user *)arg); if (retval ==0) { zl380twEnterCritical(zl380tw); retval = zl380tw_reset(zl380tw, buf); zl380twExitCritical(zl380tw); } break; case TWOLF_SAVE_FWR_TO_FLASH : zl380twEnterCritical(zl380tw); retval = zl380tw_save_image_to_flash(zl380tw); zl380twExitCritical(zl380tw); break; case TWOLF_LOAD_FWR_FROM_FLASH : retval = __get_user(buf, (u16 __user *)arg); if (retval ==0) { zl380twEnterCritical(zl380tw); retval = zl380tw_load_fwr_from_flash(zl380tw, buf); zl380twExitCritical(zl380tw); } break; case TWOLF_SAVE_CFG_TO_FLASH : zl380twEnterCritical(zl380tw); retval = zl380tw_save_cfg_to_flash(zl380tw); zl380twExitCritical(zl380tw); break; case TWOLF_LOAD_CFG_FROM_FLASH : retval = __get_user(buf, (u16 __user *)arg); if (retval ==0) { zl380twEnterCritical(zl380tw); retval = zl380tw_load_cfg_from_flash(zl380tw, buf); zl380twExitCritical(zl380tw); } break; case TWOLF_ERASE_IMGCFG_FLASH : retval = __get_user(buf, (u16 __user *)arg); if (retval ==0) { zl380twEnterCritical(zl380tw); retval = zl380tw_erase_fwrcfg_from_flash(zl380tw, buf); zl380twExitCritical(zl380tw); } break; case TWOLF_LOAD_FWRCFG_FROM_FLASH : retval = __get_user(buf, (u16 __user *)arg); if (retval ==0) { zl380twEnterCritical(zl380tw); retval = zl380tw_load_fwrcfg_from_flash(zl380tw, buf); zl380twExitCritical(zl380tw); } break; case TWOLF_HBI_WR_ARB_SINGLE_WORD : retval = __get_user(buf, (u16 __user *)arg); if (retval ==0) { zl380twEnterCritical(zl380tw); retval = zl380tw_wr16(zl380tw, buf); zl380twExitCritical(zl380tw); } break; case TWOLF_HBI_RD_ARB_SINGLE_WORD : zl380twEnterCritical(zl380tw); retval = zl380tw_rd16(zl380tw, &buf); zl380twExitCritical(zl380tw); if (retval ==0) retval = __put_user(buf, (__u16 __user *)arg); break; case TWOLF_HBI_INIT : retval = __get_user(buf, (u16 __user *)arg); if (retval ==0) { zl380twEnterCritical(zl380tw); retval = zl380tw_hbi_init(zl380tw, buf); zl380twExitCritical(zl380tw); } break; case TWOLF_ERASE_ALL_FLASH : zl380twEnterCritical(zl380tw); retval = zl380tw_reset(zl380tw, ZL38040_RST_TO_BOOT); if (retval ==0) retval = zl380tw_erase_flash(zl380tw); zl380twExitCritical(zl380tw); break; case TWOLF_STOP_FWR : zl380twEnterCritical(zl380tw); retval = zl380tw_stop_fwr_to_bootmode(zl380tw); zl380twExitCritical(zl380tw); break; case TWOLF_START_FWR : zl380twEnterCritical(zl380tw); retval = zl380tw_start_fwr_from_ram(zl380tw); zl380twExitCritical(zl380tw); break; default: printk(KERN_DEBUG "ioctl: Invalid Command Value"); retval = -EINVAL; } return retval; } /*----------------------------------------------------------------------* * The ZL38040/05x/06x/08x kernel specific aceess functions are defined below *-------------------------ZL380xx FUNCTIONs-----------------------------*/ /* This function is best used to simply retrieve pending data following a * previously sent write command * The data is returned in bytes format. Up to 256 data bytes. */ static ssize_t zl380tw_io_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { /* This access uses the spi/i2c command frame format - where both * the whole data is read in one active chip_select */ struct zl380tw_priv *zl380tw = filp->private_data; int status = 0; u16 cmd = 0; if (count > twHBImaxTransferSize) return -EMSGSIZE; #ifdef MICROSEMI_HBI_SPI if (zl380tw->spi == NULL) { TW_DEBUG1("spi device is not available \n"); return -ESHUTDOWN; } #endif #ifdef MICROSEMI_HBI_I2C if (zl380tw->i2c == NULL) { TW_DEBUG1("zl380tw_io_read::i2c device is not available \n"); return -ESHUTDOWN; } #endif zl380twEnterCritical(zl380tw); if (copy_from_user(zl380tw->pData, buf, count)) { zl380twExitCritical(zl380tw); return -EFAULT; } cmd = (*(zl380tw->pData + 0) << 8) | (*(zl380tw->pData + 1)); /*read the data*/ status = zl380tw_hbi_access(zl380tw, cmd, count, zl380tw->pData, TWOLF_HBI_READ); if (status < 0) { zl380twExitCritical(zl380tw); return -EFAULT; } if (copy_to_user(buf, zl380tw->pData, count)) { zl380twExitCritical(zl380tw); return -EFAULT; } zl380twExitCritical(zl380tw); return 0; } /* Write multiple bytes (up to 254) to the device * the data should be formatted as follows * cmd_type, * cmd_byte_low, * cmd_byte_hi, * data0_byte_low, * data0_byte_hi * ... * datan_byte_low, datan_byte_hi (n < 254) */ static ssize_t zl380tw_io_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { /* This access use the spi/i2c command frame format - where both * the command and the data to write are sent in one active chip_select */ struct zl380tw_priv *zl380tw = filp->private_data; int status = 0; u16 cmd = 0; if (count > twHBImaxTransferSize) return -EMSGSIZE; zl380twEnterCritical(zl380tw); if (copy_from_user(zl380tw->pData, buf, count)) { zl380twExitCritical(zl380tw); return -EFAULT; } cmd = (*(zl380tw->pData + 0) << 8) | (*(zl380tw->pData + 1)); TW_DEBUG2("count = %d\n",count); /*remove the cmd numbytes from the count*/ status = zl380tw_hbi_access(zl380tw, cmd, count-2, (zl380tw->pData+2), TWOLF_HBI_WRITE); if (status < 0) { zl380twExitCritical(zl380tw); return -EFAULT; } zl380twExitCritical(zl380tw); return status; } static int zl380tw_io_open(struct inode *inode, struct file *filp) { struct zl380tw_priv *zl380tw; int status = -ENXIO; mutex_lock(&zl380tw_list_lock); list_for_each_entry(zl380tw, &zl380tw_list, device_entry) { if (zl380tw->t_dev == inode->i_rdev) { status = 0; break; } } pr_debug("status %d of zl380tw_io_open", status); if (status != 0) { mutex_unlock(&zl380tw_list_lock); return status; } /* Allocating memory for the data buffer if not already done*/ if (!zl380tw->pData) { zl380tw->pData = kmalloc(twHBImaxTransferSize, GFP_KERNEL); if (!zl380tw->pData) { pr_debug("Error allocating %d bytes pdata memory", twHBImaxTransferSize); mutex_unlock(&zl380tw_list_lock); return -ENOMEM; } memset(zl380tw->pData, 0, twHBImaxTransferSize); } module_usage_count++; filp->private_data = zl380tw; mutex_unlock(&zl380tw_list_lock); return 0; } static int zl380tw_io_close(struct inode *inode, struct file *filp) { struct zl380tw_priv *zl380tw; mutex_lock(&zl380tw_list_lock); zl380tw = filp->private_data; filp->private_data = NULL; if (module_usage_count) { module_usage_count--; } if (!module_usage_count) { kfree((void *)zl380tw->pData); zl380tw->pData = NULL; } mutex_unlock(&zl380tw_list_lock); return 0; } static const struct file_operations zl380tw_fops = { .owner = THIS_MODULE, .open = zl380tw_io_open, .read = zl380tw_io_read, .write = zl380tw_io_write, .release = zl380tw_io_close, #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) .ioctl = zl380tw_io_ioctl #else .unlocked_ioctl = zl380tw_io_ioctl #endif }; /*----------------------------------------------------------------------------*/ static struct class *zl380tw_class; #endif /*ZL380XX_TW_ENABLE_CHAR_DEV_DRIVER */ /*-------------------------------------------------------------------- * CHARACTER type Host Interface driver - END *--------------------------------------------------------------------*/ /*-------------------------------------------------------------------- * SPI/I2C driver registration *--------------------------------------------------------------------*/ #ifdef ZL380TW_DEV_BIND_SLAVE_TO_MASTER /* Bind the client driver to a master adapter. This is normally done at your board init code *If you already have spi_board_info[] done at your ./arch/../board init code with the * then, undefine this macro ZL380TW_DEV_BIND_SLAVE_TO_MASTER in zl380tw.h.... */ #ifdef MICROSEMI_HBI_SPI static int zl380xx_slaveMaster_binding(void) { struct spi_device *client; struct spi_board_info spi_device_info[] = { { .modalias = "zl380tw0", .max_speed_hz = SPIM_CLK_SPEED, .bus_num = SPIM_BUS_NUM0, .chip_select = SPIM_CHIP_SELECT0, .mode = SPIM_MODE, }, #if (NUMBER_OF_ZL380xx_DEVICES > 1) /*expand this definition to add more spi slave devices*/ { .modalias = "zl380tw1", .max_speed_hz = SPIM_CLK_SPEED, .bus_num = SPIM_BUS_NUM1, .chip_select = SPIM_CHIP_SELECT1, .mode = SPIM_MODE, }, #endif }; struct spi_master *master; int i = 0; /* create a new slave device, given the master and device info * if another device is alread assigned to that bus.cs, then this will fail */ for (i = 0; i< ARRAY_SIZE(spi_device_info); i++) { /* get the master device, given SPI the bus number*/ master = spi_busnum_to_master( spi_device_info[i].bus_num ); if( !master ) return -ENODEV; printk(KERN_INFO "SPI master device found at bus = %d\n", master->bus_num); client = spi_new_device( master, &spi_device_info[i] ); if( !client ) return -ENODEV; printk(KERN_INFO "SPI slave device %s, module alias %s added at bus = %d, cs =%d\n", client->dev.driver->name, client->modalias, client->master->bus_num, client->chip_select); } return 0; } #else /*#ifdef MICROSEMI_HBI_I2C*/ /* Bind the client driver to a master adapter. This is normally done at your board init code via * i2c_register_board_info() *If you already have this done at your ./arch/../board init code with the i2c_register_board_info * then, undefine this macro ZL380TW_DEV_BIND_SLAVE_TO_MASTER in zl380tw.h.... */ static int zl380xx_slaveMaster_binding(void) { struct i2c_client *client; struct i2c_board_info i2c_device_info[] = { {I2C_BOARD_INFO("zl380tw0", MICROSEMI_I2C_ADDR0), .platform_data = NULL,}, #if (NUMBER_OF_ZL380xx_DEVICES > 1) /*expand this definition for more slave i2c devices*/ {I2C_BOARD_INFO("zl380tw1", MICROSEMI_I2C_ADDR1), .platform_data = NULL,}, #endif }; int i = 0; /*create a new client for that adapter*/ printk("Searching on the Address %d \n",CONTROLLER_I2C_BUS_NUM); for (i = 0; i< ARRAY_SIZE(i2c_device_info); i++) { /*get the i2c adapter*/ struct i2c_adapter *master = i2c_get_adapter(CONTROLLER_I2C_BUS_NUM); if( !master ) return -ENODEV; printk(KERN_INFO "found I2C master device %s \n", master->name); client = i2c_new_device( master, &i2c_device_info[i] ); if( !client) return -ENODEV; printk(KERN_INFO "I2C slave device %s, module alias %s attached to I2C master device %s\n", client->dev.driver->name, client->name, master->name); } return 0; } #endif /*MICROSEMI_HBI_SPI/I2C interface selection macro*/ #endif /*#ZL380TW_DEV_BIND_SLAVE_TO_MASTER*/ /*create the /dev char device nodes one per minor number * nodes wil be created as device0: zl380tw.0, device1: zl380tw.1,....devicen: zl380tw.n */ static int zl380tw_sub_probe(struct zl380tw_priv *zl380tw) { int status =0 ; printk("---------------------->Inside the Zl Probe Function Before Char Driver\n"); #ifdef ZL380XX_TW_ENABLE_CHAR_DEV_DRIVER unsigned long dev_minor; unsigned long dtmfdev_minor; printk("---------------------->Inside the Zl Probe Function \n"); mutex_init(&zl380tw->hbi_lock); INIT_LIST_HEAD(&zl380tw->device_entry); mutex_lock(&zl380tw_list_lock); dev_minor = find_first_zero_bit(char_minors, NUMBER_OF_ZL380xx_DEVICES); if (dev_minor < NUMBER_OF_ZL380xx_DEVICES) { struct device *dev; zl380tw->t_dev = MKDEV(MAJOR(t_dev), dev_minor); dev = device_create(zl380tw_class, #ifdef MICROSEMI_HBI_SPI &zl380tw->spi->dev, #else &zl380tw->i2c->dev, #endif zl380tw->t_dev, zl380tw, "zl380tw.%lu", dev_minor); status = IS_ERR(dev) ? PTR_ERR(dev) : 0; } else { status = -ENODEV; } if (status == 0) { set_bit(dev_minor, char_minors); list_add(&zl380tw->device_entry, &zl380tw_list); } printk("---------------------->Inside the Zl Probe Function:Code sanju_version 006 \n"); printk("---------------------->Inside the Zl Probe Function:3 \n"); mutex_unlock(&zl380tw_list_lock); #endif return status; } static int zl380tw_sub_remove(struct zl380tw_priv *zl380tw) { #ifdef ZL380TW_DEV_BIND_SLAVE_TO_MASTER #ifdef MICROSEMI_HBI_SPI spi_unregister_device(zl380tw->spi); #else i2c_unregister_device(zl380tw->i2c); #endif #endif /*ZL380GA_SPI_BIND_SLAVE_TO_MASTER*/ #ifdef ZL380XX_TW_ENABLE_CHAR_DEV_DRIVER mutex_lock(&zl380tw_list_lock); list_del(&zl380tw->device_entry); device_destroy(zl380tw_class, zl380tw->t_dev); clear_bit(MINOR(zl380tw->t_dev), char_minors); if (module_usage_count == 0) kfree(zl380tw); mutex_unlock(&zl380tw_list_lock); #endif return 0; } #ifdef MICROSEMI_HBI_SPI static int zl380tw_spi_probe(struct spi_device *spi) { int status = 0; struct zl380tw_priv *zl380tw; dev_dbg(&spi->dev, "probing zl380tw spi device\n"); /* Allocate memory for the driver data */ zl380tw = kzalloc(sizeof(*zl380tw), GFP_KERNEL); if (zl380tw == NULL) return -ENOMEM; zl380tw->spi = spi; status = zl380tw_sub_probe(zl380tw); if (status < 0) { dev_dbg(&spi->dev, "all minor number are in use!!\n"); return -ENODEV; } spi->mode = SPIM_MODE; spi->max_speed_hz = SPIM_CLK_SPEED; spi->bits_per_word = 8; status = spi_setup(spi); if (status < 0) { kfree(zl380tw); return status; } /* Initialize the driver data */ spi_set_drvdata(spi, zl380tw); printk(KERN_ERR "SPI slave device found at bus::cs = %d::%d\n", spi->master->bus_num, spi->chip_select); #ifdef ZL380XX_TW_ENABLE_ALSA_CODEC_DRIVER status = snd_soc_register_codec(&spi->dev, &soc_codec_dev_zl380tw, zl380tw_dai, NUMBER_OF_ZL380xx_DEVICES); if(status < 0) { kfree(zl380tw); dev_dbg(&spi->dev, "zl380tw ALSA spi codec device not created!!!\n"); return status; } #endif #ifdef ZL380XX_TW_UPDATE_FIRMWARE if (zl380tw_ldfwr(zl380tw, ZLS380TW0_TWOLF) < 0) { dev_dbg(&spi->dev, "error loading the firmware into the codec\n"); #ifdef ZL380XX_TW_ENABLE_ALSA_CODEC_DRIVER snd_soc_unregister_codec(&spi->dev); #endif kfree(zl380tw); return -ENODEV; } #endif dev_dbg(&spi->dev, "zl380tw codec device created...\n"); return 0; } static int zl380tw_spi_remove(struct spi_device *spi) { struct zl380tw_priv *zl380tw = spi_get_drvdata(spi); #ifdef ZL380XX_TW_ENABLE_ALSA_CODEC_DRIVER snd_soc_unregister_codec(&spi->dev); #endif zl380tw_sub_remove(zl380tw); return 0; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)) /*IMPORTANT note: Change this controller string maching accordingly per your *.dts or *dtsi compatible definition file*/ static struct of_device_id zl380tw_of_match[] = { { .compatible = "ms,zl380tw",}, {}, }; MODULE_DEVICE_TABLE(of, zl380tw_of_match); #endif static const struct spi_device_id zl380tw_spi_ids[] = { {"zl380tw0", 0 }, #if (NUMBER_OF_ZL380xx_DEVICES > 1) {"zl380tw1", 0 }, /*Expand this structure here in accordance to the number of Timberwolf devices to support */ #endif {} }; MODULE_DEVICE_TABLE(spi, zl380tw_spi_ids); static struct spi_driver zl380tw_spi_driver = { .driver = { .name = "zl380tw", .owner = THIS_MODULE, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)) .of_match_table = zl380tw_of_match, #endif }, .id_table = zl380tw_spi_ids, .probe = zl380tw_spi_probe, .remove = zl380tw_spi_remove, }; #endif #ifdef MICROSEMI_HBI_I2C static int zl380tw_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { int status = 0; struct zl380tw_priv *zl380tw; /* Allocate memory for the driver data */ zl380tw = kzalloc(sizeof(*zl380tw), GFP_KERNEL); if (zl380tw == NULL) return -ENOMEM; zl380tw->i2c = i2c; status = zl380tw_sub_probe(zl380tw); if (status < 0) { dev_dbg(&i2c->dev, "all minor number are in use!!\n"); return -ENODEV; } i2c_set_clientdata(i2c, zl380tw); printk(KERN_ERR "I2C slave device found at address = 0x%04x\n", i2c->addr); #ifdef ZL380XX_TW_ENABLE_ALSA_CODEC_DRIVER status = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_zl380tw, zl380tw_dai, NUMBER_OF_ZL380xx_DEVICES); printk("ZL380XX: snd_soc_register_codec\n"); if(status < 0) { kfree(zl380tw); dev_dbg(&i2c->dev, "zl380tw I2c device not created!!!\n"); return status; } #endif #ifdef ZL380XX_TW_UPDATE_FIRMWARE if (zl380tw_ldfwr(zl380tw, ZLS380TW0_TWOLF) < 0) { dev_dbg(&i2c->dev, "error loading the firmware into the codec\n"); #ifdef ZL380XX_TW_ENABLE_ALSA_CODEC_DRIVER snd_soc_unregister_codec(&i2c->dev); #endif kfree(zl380tw); return -ENODEV; } #endif printk("Micro Semi Codec is Initiated Successfully\n"); dev_dbg(&i2c->dev, "zl380tw I2C codec device created...\n"); return status; } static int zl380tw_i2c_remove(struct i2c_client *i2c) { struct zl380tw_priv *zl380tw = i2c_get_clientdata(i2c); #ifdef ZL380XX_TW_ENABLE_ALSA_CODEC_DRIVER snd_soc_unregister_codec(&i2c->dev); #endif //kfree(i2c_get_clientdata(i2c)); zl380tw_sub_remove(zl380tw); return 0; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)) /*IMPORTANT note: Change this controller string maching accordingly per your *.dts or *dtsi compatible definition file*/ static struct of_device_id zl380tw_of_match[] = { { .compatible = "ms,zl380tw",}, {}, }; MODULE_DEVICE_TABLE(of, zl380tw_of_match); #endif static struct i2c_device_id zl380tw_i2c_ids[] = { {"zl380tw0", 0 }, #if (NUMBER_OF_ZL380xx_DEVICES > 1) {"zl380tw1", 0 }, /*Expand this structure here in accordance to the number of Timberwolf ddevices to support */ #endif {} }; MODULE_DEVICE_TABLE(i2c, zl380tw_i2c_ids); static struct i2c_driver zl380tw_i2c_driver = { .driver = { .name = "zl380tw", .owner = THIS_MODULE, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)) .of_match_table = zl380tw_of_match, #endif }, .probe = zl380tw_i2c_probe, .remove = zl380tw_i2c_remove, .id_table = zl380tw_i2c_ids, }; #endif #if 0 dev_t dtmf_p_devno; /* Our first device number */ /* * Set up a cdev entry. */ static void dtmf_p_setup_cdev(struct dtmf_pipe *dev, int index) { int err, devno = t_dtmfdev + index; cdev_init(&dev->cdev, &fops_dtmf); dev->cdev.owner = THIS_MODULE; err = cdev_add (&dev->cdev, devno, 1); /* Fail gracefully if need be */ if (err) printk(KERN_NOTICE "Error %d adding scullpipe%d", err, index); } #endif static int __init zl380tw_init(void) { #ifdef ZL380XX_TW_ENABLE_CHAR_DEV_DRIVER int status = 0; printk ("Allocating the Char Dev Region\n"); status = alloc_chrdev_region(&t_dev, 0, NUMBER_OF_ZL380xx_DEVICES, "zl380tw"); if (status < 0) { printk(KERN_ERR "Failed to register character device"); return status; } printk ("creating Class\n"); /*create the device class*/ zl380tw_class = class_create(THIS_MODULE, "zl380tw"); if (zl380tw_class == NULL) { printk(KERN_ERR "Error %d creating class zl380tw", status); unregister_chrdev_region(t_dev, NUMBER_OF_ZL380xx_DEVICES); return -1; } printk ("cdev init\n"); /* Initialize the character device */ cdev_init(&c_dev, &zl380tw_fops); /* addding the character device */ status = cdev_add(&c_dev, t_dev, NUMBER_OF_ZL380xx_DEVICES); if (status < 0) { printk(KERN_ERR "Error %d adding zl380tw", status); class_destroy(zl380tw_class); cdev_del(&c_dev); unregister_chrdev_region(t_dev, NUMBER_OF_ZL380xx_DEVICES); return -1; } printk ("cdev init end\n"); #if 0 /* * This will allocate a DTMF device for Writing and Reading the Data * */ printk( "Initializing the DTMF Device 1122\n"); // Try to dynamically allocate a major number for the device -- more difficult but worth it status = alloc_chrdev_region(&t_dtmfdev, 0, 1, "zldtmf"); if (status<0){ printk(KERN_ALERT "[DTMF] failed to register a major number\n"); return status; } /*create the device class*/ dtmfClass = class_create(THIS_MODULE, "audio2"); if (dtmfClass == NULL) { printk(KERN_ERR "Error %d creating class zl380tw", status); unregister_chrdev_region(t_dtmfdev, 1); return -1; } printk(KERN_INFO "[DTMF]: registered correctly with major number %d\n", majorNumber); printk(KERN_INFO "[DTMF]: device class registered correctly\n"); //Initializing the DTMF Pipe dtmfdatabuffer = kmalloc(sizeof(struct dtmf_pipe), GFP_KERNEL); if (dtmfdatabuffer == NULL) { unregister_chrdev(majorNumber, DEVICE_NAME); printk(KERN_ALERT "Failed to register device class\n"); return -1; } memset(dtmfdatabuffer, 0, sizeof(struct dtmf_pipe)); //Queue initializations init_waitqueue_head(&(dtmfdatabuffer->inq)); init_waitqueue_head(&(dtmfdatabuffer->outq)); /* * Semaphore Initialization * */ sema_init(&dtmfdatabuffer->sem, 1); dtmf_p_setup_cdev(dtmfdatabuffer, 0); printk("Dtmf Device pointer %p:%p\n",dtmfdatabuffer,dtmfdatabuffer->t_dtmfdev); printk("[DTMF]: device class created correctly\n"); // Made it! device was initialized printk(">>>>>>>>>>>>>>>>>>>>>>>>>Initilizing the DTMF driver\n"); /* * Initialize the DTMF Device * */ initialize_dtmf_buffer(); //Initialize the DTMF Database #endif #endif #ifdef MICROSEMI_HBI_SPI printk("Initializing the SPI\n"); status = spi_register_driver(&zl380tw_spi_driver); #endif #ifdef MICROSEMI_HBI_I2C printk("Initializing the I2CHBIdriver\n"); status = i2c_add_driver(&zl380tw_i2c_driver); printk("After HBI i2c add driver \n"); #endif if (status < 0) { printk("Error \n"); class_destroy(zl380tw_class); cdev_del(&c_dev); unregister_chrdev_region(t_dev, NUMBER_OF_ZL380xx_DEVICES); } #ifdef ZL380TW_DEV_BIND_SLAVE_TO_MASTER printk("Initializing theSlave Maser Binding\n"); status = zl380xx_slaveMaster_binding(); if (status < 0) { printk(KERN_ERR "ret =%d\n", status); return -1; } status =0; printk("Codec Initialized Success fully\n"); #else printk ("Inside the Else of the Slave Binding\n"); #endif return status; } module_init(zl380tw_init); static void __exit zl380tw_exit(void) { #ifdef MICROSEMI_HBI_SPI spi_unregister_driver(&zl380tw_spi_driver); #endif #ifdef MICROSEMI_HBI_I2C i2c_del_driver(&zl380tw_i2c_driver); #endif #ifdef ZL380XX_TW_ENABLE_CHAR_DEV_DRIVER class_destroy(zl380tw_class); cdev_del(&c_dev); unregister_chrdev_region(t_dev, NUMBER_OF_ZL380xx_DEVICES); printk(">>>>>>>>>>>>>>>>>Initializing the DTMF Database:Sanju\n"); #endif } module_exit(zl380tw_exit); MODULE_AUTHOR("Jean Bony "); MODULE_DESCRIPTION(" Microsemi Timberwolf i2c/spi/char/alsa codec driver"); MODULE_LICENSE("GPL");